Reuse audit_log.log_action with details JSONB (X5 §4, no new table) for end-to-end audit + block→source provenance. GAP-17 drift = blocks_stale flag + health-check (not fragile DOCX→blocks reparse). GAP-20 = structural case_law_id resolution (not Hebrew citation NLP). Verified vs 3+ sources (append-only lineage event; GitOps drift detect-don't-auto-remediate). Pure-code, no migration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
123 lines
9.9 KiB
Markdown
123 lines
9.9 KiB
Markdown
# FU-7 — Audit-Trail + Provenance — עיצוב
|
||
|
||
**סטטוס:** מאושר-לעיצוב · **תאריך:** 2026-05-30 · **ענף:** TBD
|
||
**מכסה:** GAP-17, GAP-18, GAP-19, GAP-20 · **מספק:** INV-AUD1, INV-AUD2, INV-AUD3, INV-EX1, INV-G9
|
||
**מקורות:** [X5-audit-provenance.md](../../spec/X5-audit-provenance.md), [06-export.md](../../spec/06-export.md), [gap-audit.md](../../spec/gap-audit.md)
|
||
**משימה:** TaskMaster #65 · **תלוי ב:** FU-1 (#59) · **סוג:** pure-code (schema-additive קל)
|
||
**מיגרציה:** אין. כל השינויים forward-only; backfill קל אופציונלי (provenance של בלוקים קיימים לא נאכף רטרואקטיבית).
|
||
|
||
---
|
||
|
||
## 1. מטרה והיקף
|
||
|
||
X5 §4 קובע את המנגנון הקנוני: **שימוש חוזר ב-`audit_log.log_action` עם `details` JSONB** —
|
||
לא טבלה חדשה (כלל-הנדסה "סימטריה"). FU-7 ממיר את `audit_log` מ"כמעט-ריק" ל-audit-trail מקצה-לקצה,
|
||
מוסיף provenance בלוק→מקורות, אוכף ציטוט→קורפוס, ומגלה drift בין DOCX-החי לבלוקים.
|
||
|
||
| GAP | בעיה (מאומת בקוד) | יעד FU-7 |
|
||
|-----|--------------------|----------|
|
||
| GAP-18 | `log_action` נכתב רק ב-`case_subtype_override` (cases.py:203) | קריאות `log_action` ב-4 פעולות משנות-מצב: upload, extract_claims, write_block, export |
|
||
| GAP-19 | `decision_blocks` נושא `model_used` בלבד — אין קישור לקטעי-מקור | רשומת provenance ב-`audit_log.details` עם source ids שהזינו את הגנרציה |
|
||
| GAP-20 | אין אכיפה שציטוט פתיר לקורפוס | ולידציה דטרמיניסטית של `case_law_id` בציטוטים → flag לבלתי-פתירים |
|
||
| GAP-17 | `active_draft_path` הופך SoT אחרי revise/apply בלי re-sync לבלוקים | דגל `blocks_stale` דטרמיניסטי + חשיפת drift ב-health-check (לא re-sync שביר) |
|
||
|
||
## 2. הכרעות אדריכליות (מאומתות ≥3 מקורות)
|
||
|
||
| החלטה | נימוק | מקורות |
|
||
|-------|--------|--------|
|
||
| provenance כ-**event ב-`audit_log` append-only** (details payload), לא עמודה/טבלה חדשה | דפוס lineage בוגר: entity-key + event-type + actor + source-ids; X5 §4 (סימטריה) | Snowflake data-lineage; OvalEdge provenance; DesignGurus append-only audit |
|
||
| GAP-17 = **detect + flag**, מקור-אמת=בלוקים, לא auto-resync | auto-remediation דורש rollback אמין; reparse DOCX→blocks שביר (edits שוברים מבנה) | Flux GitOps drift; Terraform drift (env0); Spacelift |
|
||
| GAP-20 = **ולידציה מבנית** של `case_law_id` פתיר, לא NLP של ציטוט חופשי | NLP-ציטוט עברי חופף ל-`extract_internal_citations` הקיים; INV-AUD3 מנוסח סביב פתירוּת `case_law_id` | X5 INV-AUD3; RAG attribution (Lewis 2020); ISO 8000 |
|
||
| audit כ-**non-fatal** (כשל-audit מתעד warning, לא מפיל פעולה) | git הוא שכבת-השלמות (X5 §2.1); audit_log הוא observability "מי/מה/מתי" | X5 §2.1; דפוס audit fire-safe |
|
||
|
||
## 3. הקבצים
|
||
|
||
- **Modify** `tools/audit.py` — אין שינוי לחתימת `log_action`; להוסיף helper `log_action_safe(...)` שעוטף ב-try/except (warning, non-fatal) כדי שכשל-audit לא יפיל את הפעולה.
|
||
- **Modify** `tools/documents.py` — `document_upload` (~:14) + `extract_claims` (~:300): קריאת `log_action_safe`.
|
||
- **Modify** `services/block_writer.py` — `write_block`/`store_block` (~:1010): לאסוף source ids מ-context builders + לכתוב audit `write_block` עם provenance.
|
||
- **Modify** `tools/drafting.py` — `export_docx` (~:384): audit `export_docx`; `revise_draft` (~:647) + `apply_user_edit` (~:569): סימון `blocks_stale=true`.
|
||
- **Modify** `services/db.py` — מיגרציה V22: עמודת `cases.blocks_stale boolean DEFAULT false`; helper `mark_blocks_stale(case_id, val)`; helper `resolve_citation_case_law_ids(ids)` (בדיקת קיום); helper `audit_provenance_query` (קריאה — לא חובה).
|
||
- **Modify** `services/qa_validator.py` (או היכן שרץ QA) — בדיקת ציטוט→קורפוס: לכל `case_law_id` בציטוטי-הבלוק, אם לא פתיר → ממצא-QA (warning) + audit `citation_unresolved`.
|
||
- **Modify** health-check (metrics.py / processing_status) — חשיפת `cases_with_stale_blocks` count.
|
||
- **Test** `tests/test_audit_provenance.py` (חדש) — offline, monkeypatched.
|
||
|
||
**גבול:** אין שינוי לחתימות ציבוריות; אין מיגרציית-נתונים. provenance של בלוקים *קיימים* לא נאכף
|
||
רטרואקטיבית (forward-only) — תואם FU-1/FU-2a.
|
||
|
||
## 4. GAP-18 — audit על כל פעולה משנה-מצב
|
||
|
||
`log_action_safe(action, case_id=, document_id=, details=, user=)` — עטיפת `log_action` ב-try/except
|
||
(כשל → `logger.warning`, ה-action ממשיך). נקודות-הקריאה:
|
||
|
||
| פעולה | action | details |
|
||
|-------|--------|---------|
|
||
| document_upload | `"document_upload"` | `{title, doc_type, classification}` |
|
||
| extract_claims | `"extract_claims"` | `{docs_processed, claims_count}` |
|
||
| write_block (GAP-19) | `"write_block"` | `{decision_id, block_id, model_used, generation_type, source_document_ids, retrieved_case_law_ids, claim_ids}` |
|
||
| export_docx | `"export_docx"` | `{path, file_size, block_count}` |
|
||
|
||
## 5. GAP-19 — provenance בלוק→מקורות
|
||
|
||
`write_block` כבר אוסף הקשר מ-`_build_source_context` (document chunks), `_build_precedents_context`
|
||
(`para_results`/`caselaw_rows` → `case_law_id`s), `_build_claims_context` (claim ids). היעד: לאסוף את
|
||
המזהים הללו ל-dict `sources = {document_ids, case_law_ids, claim_ids}` ולכלול אותו ברשומת ה-audit
|
||
`write_block` (§4). כך `audit_log` עונה "מאיזו פסיקה/מסמך נולד הבלוק" — בלי עמודה/טבלה חדשה.
|
||
מפתח-הקישור: `details.decision_id`+`details.block_id` (audit_log עצמו keyed ב-case_id/document_id).
|
||
|
||
## 6. GAP-20 — ציטוט→קורפוס נאכף
|
||
|
||
`resolve_citation_case_law_ids(ids) -> {resolved: [...], unresolved: [...]}` — בדיקת `EXISTS` מול
|
||
`case_law`. בנקודת ה-QA (לפני export, משתלב עם שערי FU-6): לאסוף את כל ה-`case_law_id` מציטוטי-הבלוקים
|
||
(`decision_paragraphs.citations` אם מאוכלס, אחרת מ-provenance של §5), ולהריץ resolve. בלתי-פתירים →
|
||
**ממצא-QA (warning, לא חוסם-קריטי)** + audit `citation_unresolved`. אכיפה מבנית בלבד (case_law_id),
|
||
לא חילוץ-NLP של ציטוט חופשי.
|
||
|
||
> **הערה:** `decision_paragraphs` אינו מאוכלס כיום ע"י אף כלי (ממצא Explore). לכן ולידציית-הציטוט
|
||
> פועלת על ה-`case_law_id`s שנרשמו ב-provenance (§5); אם/כאשר decision_paragraphs יאוכלס — אותה
|
||
> ולידציה חלה עליו. זה שומר את ה-GAP סגור בלי לבנות צינור-ציטוטים חדש (מחוץ-להיקף).
|
||
|
||
## 7. GAP-17 — drift בין DOCX-חי לבלוקים
|
||
|
||
מקור-אמת = `decision_blocks` (INV-EX1). אחרי `revise_draft`/`apply_user_edit` שהופכים את
|
||
`active_draft_path` ל-SoT-בפועל בלי re-sync, מסמנים `cases.blocks_stale=true` (חוזה מפורש: "הבלוקים
|
||
ידועים כלא-מסונכרנים מול ה-DOCX-החי"). `export_docx` מ-blocks מאפס `blocks_stale=false` (הבלוקים שוב SoT).
|
||
health-check חושף `cases_with_stale_blocks`. **לא** מבצעים reparse DOCX→blocks (שביר).
|
||
|
||
| נקודה | פעולה על blocks_stale |
|
||
|-------|------------------------|
|
||
| revise_draft / apply_user_edit | `= true` (DOCX-חי חרג מהבלוקים) |
|
||
| export_docx (מ-blocks) | `= false` (בלוקים = SoT שוב) |
|
||
| write_block / save_block_content | `= false` (בלוק עודכן ב-DB) |
|
||
|
||
## 8. שינויי-התנהגות וסיכון
|
||
|
||
| שינוי | השפעה | סיכון |
|
||
|--------|--------|--------|
|
||
| audit על 4 פעולות | audit_log מתמלא; observability | נמוך — non-fatal, לא משנה תוצאת-פעולה |
|
||
| provenance ב-write_block audit | רשומת מקור לכל גנרציה חדשה | נמוך — forward-only; בלוקים קיימים לא מושפעים |
|
||
| ציטוט-QA warning | ציטוט בלתי-פתיר מסומן לאימות-יו"ר | נמוך — warning, לא חוסם export (לא קריטי) |
|
||
| `blocks_stale` flag | חשיפת drift; אינו חוסם | נמוך — דגל אינפורמטיבי; V22 additive |
|
||
|
||
## 9. אסטרטגיית בדיקה
|
||
|
||
`tests/test_audit_provenance.py` — offline, monkeypatch DB pool. מקרים:
|
||
1. `log_action_safe` בולע כשל-DB (warning) ולא מרים.
|
||
2. כל אחת מ-4 הפעולות קוראת ל-audit עם ה-action הנכון (monkeypatch log_action, assert call).
|
||
3. write_block audit כולל `source_document_ids`/`retrieved_case_law_ids` מה-context.
|
||
4. `resolve_citation_case_law_ids`: מפריד resolved/unresolved נכון (monkeypatch EXISTS).
|
||
5. ציטוט בלתי-פתיר → ממצא-QA warning (לא חוסם-קריטי).
|
||
6. `blocks_stale`: revise/apply → true; export-from-blocks → false.
|
||
7. health-check חושף `cases_with_stale_blocks`.
|
||
|
||
> בדיקות-DB אמיתיות (audit_log INSERT, V22, EXISTS) — smoke מול DB מקומי (5433) בסיום, כמו FU-2a.
|
||
|
||
## 10. סדר-ביצוע
|
||
|
||
1. בדיקות אדומות.
|
||
2. `log_action_safe` + מיגרציה V22 (`blocks_stale`) + helpers (`mark_blocks_stale`, `resolve_citation_case_law_ids`).
|
||
3. GAP-18: 4 קריאות audit (upload, extract_claims, export_docx + write_block בסיס).
|
||
4. GAP-19: איסוף source ids ב-write_block → provenance ב-audit.
|
||
5. GAP-20: ולידציית-ציטוט ב-QA + audit `citation_unresolved`.
|
||
6. GAP-17: `blocks_stale` ב-revise/apply/export/write_block + health-check.
|
||
7. בדיקות ירוקות + smoke מול DB + lint + TaskMaster.
|