From 99cd6bc4dd18675844c021c62a963f1b7962a283 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 30 May 2026 21:15:50 +0000 Subject: [PATCH] docs(spec): FU-7 audit-trail + provenance design (GAP-17/18/19/20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .../2026-05-30-fu7-audit-provenance-design.md | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-30-fu7-audit-provenance-design.md diff --git a/docs/superpowers/specs/2026-05-30-fu7-audit-provenance-design.md b/docs/superpowers/specs/2026-05-30-fu7-audit-provenance-design.md new file mode 100644 index 0000000..6b73a45 --- /dev/null +++ b/docs/superpowers/specs/2026-05-30-fu7-audit-provenance-design.md @@ -0,0 +1,122 @@ +# 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.