# 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`). שני המצבים שולפים מאותה טבלה — וריאציית-תצוגה
> של אותו מקור-אמת, לא מסלול שני.
>
> **דטרמיניזם-מבני של טיוטת-הביניים (#204 / WS5).** סדר-בלוקי-הביניים בייצוא קבוע
> ב-`docx_exporter._INTERIM_BLOCK_ORDER` (רקע→תכניות→טענות→הליכים, עם בלוקי-תבנית א–ה
> אופציונליים בתחילה). הבלוקים עצמם נכתבים דרך **אותו מסלול-כתיבה קנוני** של ההחלטה
> המלאה (`block_writer.write_and_store_block` → `write_block`, G2 — אין מסלול-ייצור
> מקביל), אך כעת **נעוצים למודל יחיד (`claude-opus-4-8`) עם `effort` per-בלוק ובפרומפט-
> מבני קבוע** (למשל בלוק-ה פותח תמיד ב"לפנינו ערר…"). התוצאה: אותו תיק מפיק את אותו
> **מבנה** בכל ריצה (אי-עקביות-הניסוח הקודמת נבעה מפרומפט-חופשי + מודל-ברירת-מחדל לא-נעוץ).
> טמפרטורה אינה במסלול (Opus 4.7/4.8 דוחים אותה); ראה `block_writer.GENERATION_MODEL`
> + `BLOCK_CONFIG[*].effort`.
> - `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 מסומן `` (`_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 · חסום בכשל-קריטי · עובר כשנקי).
### INV-EX4: מקור-תבנית-יחיד — כל מפיק-מסמך מחיל סגנונות-טמפלט בלבד (→G2)
**כלל:** **כל** מפיק של מסמך-Word הקשור-להחלטה — סופי (`export_docx`), טיוטת-ביניים
(`export_interim_draft`), סיכום-מנהלים (`party-claims-summary`), ומסמך-הניתוח
(`build_analysis_docx`), וכן מסלול-העריכה (`revise_draft`/`apply_user_edit` →
`docx_reviser`) — **חייב לטעון את הטמפלט היחיד** `skills/docx/decision_template.docx`
ולהחיל עיצוב **אך-ורק דרך שמות-הסגנונות שמוגדרים בו** (`paragraph.style = "…"` /
``). **אסור hand-setting** של משפחת-גופן (`rFonts`), גודל-גופן
(`sz`/`szCs`), או שוליים — אלה נגזרים מ-`styles.xml` של הטמפלט (cascade). היחיד שמותר
לכפות מפורשות הוא **יישור-הגוף הקנוני `jc=both`** היכן שהוא דורס סגנון שמגיע ממורכז
(`_set_paragraph_jc`, `docx_exporter.py:80`) — כי זו ההתנהגות-הקנונית, לא עיצוב-אד-הוק.
סמני-כיווניות (``, run ``) מותרים (כיווניות, לא עיצוב). **טמפלט אחד = מקור-סגנון
יחיד; אין שכבת-סגנון מקבילה.**
**מקורות:** SSOT (Single Source of Truth) · Martin Fowler — Canonical Data Model · ECMA-376
(OOXML) §17.7.2 — הורשת-סגנונות-פסקה (paragraph style inheritance / cascade) | סטטוס: verified
**אכיפה:** `docx_exporter` ו-`analysis_docx_exporter` טוענים `TEMPLATE_PATH` ומחילים style-by-name
(ראה §2); `docx_reviser` מחיל `pStyle` מן-הטמפלט + `jc=both` (לא פונט/גודל ידני). שומר-CI
`test_docx_template_single_source.py` גורף את מודולי-מפיקי-ה-DOCX ונכשל על `rFonts`/`sz`/`szCs`
ידני מחוץ לטוען-הטמפלט. אוכף את
[INV-G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) בשכבת-הרינדור.
**הפרה ידועה — ✅ נסגרה:** `docx_reviser._build_paragraph` כפה `jc=right` + David/`sz` ידני על כל
פסקה שהוכנסה במסלול-העריכה (`revise_draft`/`apply_user_edit`) → פלט מסלול-העריכה איבד את היישור
הדו-צדדי של הטמפלט והכניס עיצוב-אד-הוק. נסגר ע"י החלת `pStyle` מן-הטמפלט; מאומת ב-
`test_docx_reviser.py` + `test_docx_template_single_source.py`.
---
## 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).