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>
9.9 KiB
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, 06-export.md, 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; להוסיף helperlog_action_safe(...)שעוטף ב-try/except (warning, non-fatal) כדי שכשל-audit לא יפיל את הפעולה. - Modify
tools/documents.py—document_upload(:14) +:300): קריאתextract_claims(log_action_safe. - Modify
services/block_writer.py—write_block/store_block(~:1010): לאסוף source ids מ-context builders + לכתוב auditwrite_blockעם provenance. - Modify
tools/drafting.py—export_docx(:384): audit:647) +export_docx;revise_draft(apply_user_edit(~:569): סימוןblocks_stale=true. - Modify
services/db.py— מיגרציה V22: עמודתcases.blocks_stale boolean DEFAULT false; helpermark_blocks_stale(case_id, val); helperresolve_citation_case_law_ids(ids)(בדיקת קיום); helperaudit_provenance_query(קריאה — לא חובה). - Modify
services/qa_validator.py(או היכן שרץ QA) — בדיקת ציטוט→קורפוס: לכלcase_law_idבציטוטי-הבלוק, אם לא פתיר → ממצא-QA (warning) + auditcitation_unresolved. - Modify health-check (metrics.py / processing_status) — חשיפת
cases_with_stale_blockscount. - 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_ids), _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_ids שנרשמו ב-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. מקרים:
log_action_safeבולע כשל-DB (warning) ולא מרים.- כל אחת מ-4 הפעולות קוראת ל-audit עם ה-action הנכון (monkeypatch log_action, assert call).
- write_block audit כולל
source_document_ids/retrieved_case_law_idsמה-context. resolve_citation_case_law_ids: מפריד resolved/unresolved נכון (monkeypatch EXISTS).- ציטוט בלתי-פתיר → ממצא-QA warning (לא חוסם-קריטי).
blocks_stale: revise/apply → true; export-from-blocks → false.- health-check חושף
cases_with_stale_blocks.
בדיקות-DB אמיתיות (audit_log INSERT, V22, EXISTS) — smoke מול DB מקומי (5433) בסיום, כמו FU-2a.
10. סדר-ביצוע
- בדיקות אדומות.
log_action_safe+ מיגרציה V22 (blocks_stale) + helpers (mark_blocks_stale,resolve_citation_case_law_ids).- GAP-18: 4 קריאות audit (upload, extract_claims, export_docx + write_block בסיס).
- GAP-19: איסוף source ids ב-write_block → provenance ב-audit.
- GAP-20: ולידציית-ציטוט ב-QA + audit
citation_unresolved. - GAP-17:
blocks_staleב-revise/apply/export/write_block + health-check. - בדיקות ירוקות + smoke מול DB + lint + TaskMaster.