Files
legal-ai/docs/superpowers/specs/2026-05-30-fu7-audit-provenance-design.md
Chaim 99cd6bc4dd docs(spec): FU-7 audit-trail + provenance design (GAP-17/18/19/20)
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>
2026-05-30 21:15:50 +00:00

123 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.