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

173 lines
14 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.
# 06 — ייצוא DOCX (Export Contract)
קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומגדיר את **חוזה-הייצוא** של עוזר
משפטי: הרינדור של החלטה ל-DOCX מעוצב (גופן David, RTL, סגנונות-טמפלט). העיקרון המכונן —
**ה-DB הוא מקור-האמת היחיד, וה-DOCX הוא נתון נגזר (derived) הניתן לשחזור**. הקובץ אוכף את
[INV-G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) (מקור-אמת
יחיד / נתון-נגזר משוחזר) ואת [INV-G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai)
(עקיבוּת-מקור), והוא השלב שאחרי שער-הייצוא הקריטי של
[05-qa-review.md / INV-QA3](05-qa-review.md#inv-qa3-החלטה-לא-מיוצאת-עם-כשל-קריטי-governance--g10).
> **כללי-סגנון — סמכות אחת.** מכניקת העיצוב (line classification, dash policy, placeholder,
> מיפוי-סגנונות, RTL-runs) מתועדת במלואה בסקיל
> [`dafna-decision-template/SKILL.md`](../../skills/dafna-decision-template/SKILL.md) — **הוא
> המקור הסמכותי**. הקובץ הזה **מסכם ומפנה**, לא משכפל. כללי-הסגנון עצמם הם תוכן-משפטי-דומייני
> (סמכות היו"ר + הסקיל), בעוד שחוזה-ה-derived-data (INV-EX1) ועקיבוּת-המקור (INV-EX2) הם
> invariants הנדסיים הנושאים `מקורות` + `סטטוס`.
---
## 1. חוזה-הייצוא — DB הוא המקור, DOCX הוא הנגזר
החלטה מאוחסנת כ-**בלוקים מובְנים ב-DB** — `decision_blocks` (12 בלוקים, מפתח קנוני
`UNIQUE(decision_id, block_id)`) תחת `decisions` (`UNIQUE(case_id, version)`); ראה
[02-data-model.md §1](02-data-model.md). ה-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](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
> (סימטריה — לא שתי שכבות-סגנון מתפצלות).
## 2. כללי-הסגנון — סיכום (הסמכות: הסקיל)
ה-service מחיל את סגנונות-הטמפלט בלבד (`paragraph.style = "Heading 2"`) — בלי font/size/indent
ידני; העיצוב (David, RTL, גדלים) מגיע מ-`styles.xml`. הפירוט המלא + ה-XML של כל סגנון:
[`SKILL.md`](../../skills/dafna-decision-template/SKILL.md) + `references/`.
- **סיווג-שורות (`_classify_line`):** כל שורה מסווגת לאחת מ-6 קטגוריות — `label_heading`,
`inline_label`, `numbered`, `bullet`, `heb_letter`, `plain` — שקובעות את הסגנון המוחל
(Heading 2 / Normal / List Paragraph). ראה
[`references/line-classification.md`](../../skills/dafna-decision-template/references/line-classification.md).
- **מדיניות-מקפים (`_no_dash`):** דפנה ביקשה "בלי מקפים בכלל" — `—` (U+2014) ו-`` (U+2013)
מוסרים מכל טקסט נכתב; מקף רגיל (`-`) נשמר.
- **שדות-placeholder:** `chair_position` עם סימן-ריק (`[ימולא ע"י יו"ר הוועדה]` וכד') מוחלף
ב-`[טרם מולאה עמדת ועדת הוועדה]` ב-italic — סימן ויזואלי שנותר להשלים (תואם
[INV-G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) — היו"ר
משלימה, לא המערכת).
- **RTL-runs:** כל run מסומן `<w:rtl/>` (`_mark_run_rtl`) — אחרת Word נופל ל-Times New Roman
במקום David. ראה [`references/rtl-runs.md`](../../skills/dafna-decision-template/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](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai); ראה
[X5-audit-provenance.md](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](00-constitution.md#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](#5-current-vs-target).
### 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](04-analysis-writing.md),
[X5-audit-provenance.md](X5-audit-provenance.md)). אוכף את
[INV-G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai).
**הפרה ידועה:**
### INV-EX3: אין ייצוא בכשל-QA קריטי (restate של INV-QA3 →G10)
**כלל:** הייצוא **חסום** כל עוד שער-QA קריטי נכשל (`claims_coverage` / `structural_integrity`);
`export_blocked` חייב להיבדק לפני ייצוא. זהו אותו invariant של
[INV-QA3](05-qa-review.md#inv-qa3-החלטה-לא-מיוצאת-עם-כשל-קריטי-governance--g10), בצד-הייצוא.
**מקורות:** 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. הפניות-אחיות
- [00-constitution.md](00-constitution.md) — [INV-G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
(derived-data / מקור-יחיד) · [INV-G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai)
(עקיבוּת) · [INV-G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) (שערים).
- [02-data-model.md](02-data-model.md) — `decisions`/`decision_blocks` (המקור שממנו מייצאים).
- [04-analysis-writing.md](04-analysis-writing.md) — כתיבת הבלוקים שמהם נגזר ה-DOCX.
- [05-qa-review.md](05-qa-review.md#inv-qa3-החלטה-לא-מיוצאת-עם-כשל-קריטי-governance--g10) —
INV-QA3 (שער-הייצוא הקריטי שקודם לשלב זה).
- [07-learning.md](07-learning.md) — `ingest_final_version` + Hermes על ההחלטה הסופית.
- [X5-audit-provenance.md](X5-audit-provenance.md) — audit-trail (commit git) ועקיבוּת-מקור.
- [`skills/dafna-decision-template/SKILL.md`](../../skills/dafna-decision-template/SKILL.md) —
**המקור הסמכותי** לכללי-הסגנון (line classification · dash policy · placeholder · RTL-runs).