Files
legal-ai/docs/spec/06-export.md
Chaim 9ef0547997
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
docs(spec): WS7 — reconcile 01/02/03/04/06 + gap-audit; verify 7 workflow-redesign leaks closed (#207)
WS7 of the workflow-redesign initiative required closing 7 known leaks along
the way. Cross-code audit verified ALL 7 were already closed in prior cycles
(FU-1/4/5/6/7 + IA waves); the spec still carried stale "known violation" text.
This PR aligns the spec to actual code state and verifies each closure with a
test. No new code path created — this is a spec-alignment audit, not a re-solve.

7 leaks → status (all closed/gated, each with a test):
1. cross-corpus halachot (#56, GAP-10/FU-4) — cl.source_kind in halacha_filters
   (db.py:7516,7519); test_precedent_corpus_isolation.py
2. invisible halacha backlog (GAP-14/FU-5) — health halacha_backlog
   (app.py:2352-2364) + extraction_status/halachot_pending
3. no eval harness (GAP-11/FU-5, G8) — scripts/eval_retrieval.py
   (P/R/MRR/nDCG vs gold-set+baseline); --self-test ALL PASS
4. export gate not hard-block (GAP-15/FU-6, INV-EX3) — export_docx checks
   qa_run_exists + get_critical_qa_failures before exporter (drafting.py:462-494);
   test_export_qa_gate.py
5. metadata on internal path (GAP-02/FU-1, INV-ING3) — unified ingest.ingest_document
   queues metadata+halacha together (ingest.py:233-234); test_unified_ingest.py
6. DOCX creep to source-of-truth (GAP-17/FU-7, INV-EX1) — active_draft_path is
   revision-anchor only; drift caught by cases.blocks_stale flag (V22)
7. UI cache-invalidation (GAP-33/FU-10) — qc.invalidateQueries on mutations

Also added forward-pointer notes for the workflow-redesign additions owned by
sibling tasks (02 documents is_primary/doc_category → #200; 04 re-analysis /
summarize_party_claims / analyze_protocol → #200/#202/#203), kept additive to
avoid line conflicts. block-schema.md interim/template edits deferred to #204/#205.

Invariants: maintains G2 (single source of truth, no parallel path — all leak
closures extend the canonical path), G8 (eval harness now exists), G10 (human
gates visible: halacha backlog + hard export gate), INV-ING3 (unified metadata
queue), INV-EX1 (DOCX is derived; blocks_stale drift flag), INV-RET1/G5 (#56
cross-corpus halacha isolation).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 11:22:11 +00:00

14 KiB
Raw Blame History

06 — ייצוא DOCX (Export Contract)

קובץ-תחום זה כפוף ל-חוקת המערכת ומגדיר את חוזה-הייצוא של עוזר משפטי: הרינדור של החלטה ל-DOCX מעוצב (גופן David, RTL, סגנונות-טמפלט). העיקרון המכונן — ה-DB הוא מקור-האמת היחיד, וה-DOCX הוא נתון נגזר (derived) הניתן לשחזור. הקובץ אוכף את INV-G2 (מקור-אמת יחיד / נתון-נגזר משוחזר) ואת INV-G9 (עקיבוּת-מקור), והוא השלב שאחרי שער-הייצוא הקריטי של 05-qa-review.md / INV-QA3.

כללי-סגנון — סמכות אחת. מכניקת העיצוב (line classification, dash policy, placeholder, מיפוי-סגנונות, RTL-runs) מתועדת במלואה בסקיל dafna-decision-template/SKILL.mdהוא המקור הסמכותי. הקובץ הזה מסכם ומפנה, לא משכפל. כללי-הסגנון עצמם הם תוכן-משפטי-דומייני (סמכות היו"ר + הסקיל), בעוד שחוזה-ה-derived-data (INV-EX1) ועקיבוּת-המקור (INV-EX2) הם invariants הנדסיים הנושאים מקורות + סטטוס.


1. חוזה-הייצוא — DB הוא המקור, DOCX הוא הנגזר

החלטה מאוחסנת כ-בלוקים מובְנים ב-DBdecision_blocks (12 בלוקים, מפתח קנוני UNIQUE(decision_id, block_id)) תחת decisions (UNIQUE(case_id, version)); ראה 02-data-model.md §1. ה-DOCX נגזר מהבלוקים האלה ואינו מקור-אמת עצמאי: מחיקתו אינה מאבדת תוכן, וייצוא חוזר מאותם בלוקים מפיק מסמך שקול.

מסלול-הייצוא הקנוני (הסופי):

  1. export_docx(case_number) (tools/drafting.py:384, נחשף server.py:557) שולף את התיק, ואז קורא ל-docx_exporter.export_decision(case_id, …, mode="final") (services/docx_exporter.py:306).
  2. export_decision שולף את הבלוקים ישירות מ-decision_blocks (SELECT block_id, block_index, title, content, word_count … ORDER BY block_index, docx_exporter.py:336-342) — אין מקור-תוכן אחר.
  3. טוען את טמפלט-דפנה (skills/docx/decision_template.docx, docx_exporter.py:27-29,364), מנקה את גוף-המסמך (_clear_body), וכותב כל בלוק עם bookmark עוטף (אנקור ל-revisions עתידיים, _wrap_block_with_bookmarks, docx_exporter.py:367-382).
  4. שומר לקובץ מגורסן data/cases/{case_number}/exports/טיוטה-v{N}.docx (גרסה אוטומטית עולה, docx_exporter.py:384-400).

שני מסלולי-ייצוא לפי מקור-התוכן (לא מסלולים-מקבילים מתפצלים):

  • docx_exporter.pyההחלטה הסופית מ-12 הבלוקים ב-decision_blocks (mode="final"), וגם טיוטת-ביניים (mode="interim" — תת-קבוצת בלוקים בסדר חדש: רקע→תכניות→טענות→הליכים, export_interim_draft, drafting.py:511). שני המצבים שולפים מאותה טבלה — וריאציית-תצוגה של אותו מקור-אמת, לא מסלול שני.
  • analysis_docx_exporter.py (build_analysis_docx, :401) — מייצא את מסמך הניתוח המשפטי (analysis-and-research.md) שכתב legal-analyst, לא את בלוקי-ההחלטה. זהו תוצר-עזר שונה (שלב ניתוח, לא החלטה) — והוא המסלול שהסקיל מתעד בעיקר. שניהם חולקים את אותו טמפלט ואותם כללי-סגנון, כנדרש מ-INV-G2 (סימטריה — לא שתי שכבות-סגנון מתפצלות).

2. כללי-הסגנון — סיכום (הסמכות: הסקיל)

ה-service מחיל את סגנונות-הטמפלט בלבד (paragraph.style = "Heading 2") — בלי font/size/indent ידני; העיצוב (David, RTL, גדלים) מגיע מ-styles.xml. הפירוט המלא + ה-XML של כל סגנון: SKILL.md + references/.

  • סיווג-שורות (_classify_line): כל שורה מסווגת לאחת מ-6 קטגוריות — label_heading, inline_label, numbered, bullet, heb_letter, plain — שקובעות את הסגנון המוחל (Heading 2 / Normal / List Paragraph). ראה references/line-classification.md.
  • מדיניות-מקפים (_no_dash): דפנה ביקשה "בלי מקפים בכלל" — (U+2014) ו- (U+2013) מוסרים מכל טקסט נכתב; מקף רגיל (-) נשמר.
  • שדות-placeholder: chair_position עם סימן-ריק ([ימולא ע"י יו"ר הוועדה] וכד') מוחלף ב-[טרם מולאה עמדת ועדת הוועדה] ב-italic — סימן ויזואלי שנותר להשלים (תואם INV-G10 — היו"ר משלימה, לא המערכת).
  • RTL-runs: כל run מסומן <w:rtl/> (_mark_run_rtl) — אחרת Word נופל ל-Times New Roman במקום David. ראה references/rtl-runs.md.
  • מספור: מספור אוטומטי רק ב-List Paragraph (decimal); שורות (א)(ב) מקבלות List Paragraph עם _strip_numpr() (המספור העברי בטקסט).

3. רישום הגרסה — active_draft_path + git

לאחר כתיבת ה-DOCX, export_docx (drafting.py:404-408):

  1. set_active_draft_path(case_id, path) (db.py:1177) — רושם את ה-DOCX שיוצא כ- active-draft הנוכחי (cases.active_draft_path, db.py:189). שדה זה הוא האנקור לעריכות עוקבות (revise_draft/apply_user_edit/list_bookmarks), לא מקור-אמת-תוכן מתחרה ל-DB.
  2. git_sync.commit_and_push(case_dir, "ייצוא DOCX: …") (drafting.py:408) — מקבע את הקובץ ב-git של תיקיית-התיק (audit-trail של פלט, INV-G9; ראה X5-audit-provenance.md).

אותו דפוס (set_active_draft_path + commit) חוזר ב-export_interim_draft (drafting.py:533,536), revise_draft (drafting.py:692,695) ו-apply_user_edit (drafting.py:579,582).


4. Invariants של התחום

INV-EX1: ייצוא דטרמיניסטי ומשוחזר מהבלוקים — DOCX הוא נתון-נגזר (→G2)

כלל: הייצוא דטרמיניסטי וניתן-לשחזור מבלוקי-ההחלטה המאוחסנים ב-decision_blocks: אותם בלוקים + אותו טמפלט מפיקים מסמך שקול. ה-DOCX הוא נתון-נגזר (derived)לעולם לא מקור-אמת עצמאי. אסור מסלול-תוכן שני שכותב DOCX ממקור שאינו ה-DB; וריאציות (final/interim) הן תצוגות של אותו מקור. מקורות: Martin Kleppmann — Designing Data-Intensive Applications (O'Reilly, 2017, system-of-record מול derived data, ושחזור derived מהמקור) · Martin Fowler (Canonical Data Model / Single Source of Truth) · SSOT (Single Source of Truth principle) | סטטוס: verified אכיפה: export_decision שולף אך-ורק מ-decision_blocks (docx_exporter.py:336-342); פלט מגורסן + idempotent מבחינת-תוכן; אוכף את INV-G2 וכלל-ההנדסה "סימטריה" (חוקה §6). הפרה ידועה — מגודרת (FU-7, GAP-17): אחרי revise_draft/apply_user_edit ה-DOCX המסומן active_draft_path משמש כאנקור לעריכות-Track-Changes העוקבות, ובלוקי-ה-DB אינם מתעדכנים חזרה — סטייה אפשרית בין הבלוקים למסמך-החי. התיקון שנבחר (חוזה מפורש, לא re-sync): active_draft_path הוא אנקור-revision בלבד, לא מקור-תוכן מתחרה — ה-DB נשאר מקור-האמת. סטייה נלכדת בדגל cases.blocks_stale (V22, db.py:1142-1148): revise_draft/apply_user_edit מסמנים mark_blocks_stale(case_id, True) (drafting.py:688,789) ו-export_docx מנקה (mark_blocks_stale(case_id, False), drafting.py:504) — הדגל הוא ה-drift-detection שחושף מתי ה-DOCX-החי נסחף מהבלוקים. ראה §5.

INV-EX2: עקיבוּת-מקור נשמרת בהחלטה המיוצאת (→G9)

כלל: ההחלטה המיוצאת שומרת על עקיבוּת-מקור היכן שנדרש — סמכויות-משפטיות מצוטטות ניתנות-לאיתור (citation resolvable), והפלט מקובע ב-audit-trail (commit git). הפניות-פסיקה בבלוקים אינן מאבדות את מקורן בעת הרינדור. מקורות: Council of Europe / CEPEJ — European Ethical Charter on AI in judicial systems (2018, traceability/transparency) · ISO 15489-1:2016 (records authenticity/integrity) · Lewis et al. (2020, NeurIPS — RAG attribution) | סטטוס: verified אכיפה: export_docx מקבע כל פלט ב-git (git_sync.commit_and_push, drafting.py:408) + רושם active_draft_path (db.py:1177); עקיבוּת-המקור של הציטוטים עצמם נאכפת במעלה-הזרם (חילוץ-טענות/הלכות + provenance, 04-analysis-writing.md, X5-audit-provenance.md). אוכף את INV-G9. הפרה ידועה:

INV-EX3: אין ייצוא בכשל-QA קריטי (restate של INV-QA3 →G10)

כלל: הייצוא חסום כל עוד שער-QA קריטי נכשל (claims_coverage / structural_integrity); export_blocked חייב להיבדק לפני ייצוא. זהו אותו invariant של INV-QA3, בצד-הייצוא. מקורות: NCSC/JTC — Principles & Practices for AI Use in Courts (controlled, auditable output) · Council of Europe / CEPEJ (2018, under user control) · Federal Judicial Center — Judicial Writing Manual (2d ed.) | סטטוס: verified אכיפה — hard-block בקוד (FU-6, GAP-15): export_docx (drafting.py:462) בודק לעצמו לפני כל ייצוא — db.qa_run_exists (אם QA לא רץ כלל → חסום) ו-db.get_critical_qa_failures (אם יש כשל-קריטי → חסום) — לפני הגישה ל-docx_exporter.export_decision. אלו SELECT זולים על ה-qa_results המאוחסנים (לא הרצת-LLM חוזרת). נוסף על export_blocked = critical_failures > 0 ברמת-הזרימה ועל משמעת-הסוכן legal-exporter (.claude/agents/legal-exporter.md:71,149) — כך שאי-אפשר לעקוף את השער אפילו בקריאה ישירה ל-export_docx. הפרה ידועה — נסגרה (FU-6, GAP-15): בעבר export_docx ניגש ישירות ל-exporter בלי לבדוק export_blocked (אכוף-זרימה בלבד, ניתן-לעקיפה). נסגר ע"י ה-hard-block לעיל; מאומת ב- test_export_qa_gate.py (חסום ללא-QA · חסום בכשל-קריטי · עובר כשנקי).


5. Current vs Target

  • שער-ייצוא — hard-block בקוד (INV-EX3 / INV-QA3, FU-6 / GAP-15). export_docx (drafting.py:462-494) בודק db.qa_run_exists + db.get_critical_qa_failures ודוחה לפני הגישה ל-docx_exporter.export_decision — לא ניתן לעקוף בקריאה ישירה. מאומת ב- test_export_qa_gate.py (3 מקרים: ללא-QA / כשל-קריטי / נקי).
  • active_draft_path כ-derived (INV-EX1, FU-7 / GAP-17) — מגודר בחוזה מפורש. ה-DB נשאר מקור-האמת; active_draft_path הוא אנקור-revision בלבד. ה-drift בין הבלוקים ל-DOCX-החי נלכד בדגל cases.blocks_stale: נדלק ב-revise_draft/apply_user_edit (drafting.py:688,789), כובה ב-export_docx (drafting.py:504). שארית (low-pri): health-check שמתריע על blocks_stale=true עתיק — תיעוד-המשך, לא חוסם.

6. הפניות-אחיות