diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json
index 20dca90..1c94cd0 100644
--- a/.taskmaster/tasks/tasks.json
+++ b/.taskmaster/tasks/tasks.json
@@ -1967,10 +1967,10 @@
"id": "56",
"title": "[Retrieval finding] halacha_filters לא מסננים source_kind — דליפה חוצת-קורפוסים",
"description": "התגלה תוך כדי משימה 53. ב-search_precedent_library_semantic וב-search_precedent_library_lexical (db.py): chunk_filters כוללים cl.source_kind=$sk אבל halacha_filters כוללים רק review_status. תוצאה: search_precedent_library(external) מחזיר גם הלכות internal_committee, ו-search_internal_decisions(internal) מחזיר גם הלכות external. אי-עקביות: chunks מסוננים, halachot לא. כרגע זה דווקא מסייע למציאוּת (לכן לא רגרסיה), אבל לא עקבי. דורש החלטת מדיניות: או לסנן halachot גם לפי source_kind (עקבי, אך 'מסתיר' שכבות), או להשאיר מאוחד במכוון + לתעד. אם משאירים מאוחד — לעדכן docstrings של שני הכלים שזה לא 'corpus נפרד'.",
- "status": "pending",
+ "status": "cancelled",
"priority": "low",
"dependencies": [],
- "details": "db.py: search_precedent_library_semantic (~שורה הקודמת ל-3311), search_precedent_library_lexical (3346). שתי הפונקציות: halacha_filters=['h.review_status IN ...'] — חסר cl.source_kind. נמצא בעת בדיקת רגרסיה למשימה 53.",
+ "details": "db.py: search_precedent_library_semantic (~שורה הקודמת ל-3311), search_precedent_library_lexical (3346). שתי הפונקציות: halacha_filters=['h.review_status IN ...'] — חסר cl.source_kind. נמצא בעת בדיקת רגרסיה למשימה 53.\n\n[SUPERSEDED 2026-05-30] נבלע ב-FU-4 / תת-משימה 62.1 (GAP-10). נסגר כדי למנוע כפילות-tracker.",
"testStrategy": "לאחר החלטה: אם מסננים — search_precedent_library('...substantive...', external) לא מחזיר case_law_id internal; אם משאירים — docstring מעודכן + טסט מאשר התנהגות מכוונת.",
"subtasks": [],
"updatedAt": "2026-05-30T11:09:30.257989+00:00"
@@ -1984,7 +1984,7 @@
"dependencies": [
"55"
],
- "details": "מקור: case_law.full_text קיים. נתיב: chunker.chunk_document(_hierarchical) → embeddings → החלפת precedent_chunks לתיק. למחוק chunks ישנים של התיק לפני הוספה. אחרי הרצה — ניתן להסיר את פילטר ה->=50 query (אופציונלי). תיקים מושפעים: SELECT DISTINCT case_law_id WHERE length(trim(content))<50.",
+ "details": "מקור: case_law.full_text קיים. נתיב: chunker.chunk_document(_hierarchical) → embeddings → החלפת precedent_chunks לתיק. למחוק chunks ישנים של התיק לפני הוספה. אחרי הרצה — ניתן להסיר את פילטר ה->=50 query (אופציונלי). תיקים מושפעים: SELECT DISTINCT case_law_id WHERE length(trim(content))<50.\n\n[קישור 2026-05-30] קשור ל-FU-3 (task 61, GAP-09 re-index). #57 = מיגרציה חד-פעמית של העבר (re-chunk legacy); FU-3 = ה-invariant הקדמי. נשמרים בנפרד במכוון.",
"testStrategy": "אחרי re-chunk לתיק לדוגמה: 0 chunks<50 לאותו case_law_id; search_internal_decisions עדיין מחזיר את התיק; ספירת chunks סבירה.",
"subtasks": [],
"updatedAt": "2026-05-30T11:19:06.142606+00:00"
@@ -2000,6 +2000,348 @@
"testStrategy": "document_list על 7 וריאציות פורמט של תיק קיים → כולן מחזירות את אותם מסמכים; תיק לא-קיים אמיתי עדיין מחזיר 'לא נמצא'.",
"subtasks": [],
"updatedAt": "2026-05-30T11:54:34.291Z"
+ },
+ {
+ "id": "59",
+ "title": "[FU-1] איחוד מסלול ה-ingest למסלול קנוני אחד",
+ "description": "מאחד את ingest_precedent ו-ingest_internal_decision למסלול קנוני יחיד; מבטל את האסימטריות.",
+ "details": "מכסה GAP-01,02,04,05. מספק INV-ING1/ING3/G2/G4. severity: Critical. סוג: קוד. יסוד — FU-2/FU-3/FU-7 תלויים בו. מקור: docs/spec/gap-audit.md + 01-ingest.md.",
+ "testStrategy": "",
+ "status": "pending",
+ "dependencies": [],
+ "priority": "high",
+ "subtasks": [
+ {
+ "id": 1,
+ "title": "[GAP-01] מסלול ingest קנוני יחיד",
+ "description": "ביטול שני המסלולים המקבילים (precedent_library.py:88 vs internal_decisions.py:73); כל סוג = פרמטרים, לא פונקציה נפרדת.",
+ "dependencies": [],
+ "details": "INV-ING1/G2",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "59"
+ },
+ {
+ "id": 2,
+ "title": "[GAP-02] תזמון חילוץ metadata לכל סוג",
+ "description": "קריאה ל-request_metadata_extraction גם במסלול הפנימי (היום רק halacha, internal_decisions.py:208).",
+ "dependencies": [],
+ "details": "INV-ING3/DM1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "59"
+ },
+ {
+ "id": 3,
+ "title": "[GAP-04] ולידציית enums אחידה",
+ "description": "הוספת ולידציית practice_area/source_type למסלול הפנימי (כמו precedent_library.py:131-134).",
+ "dependencies": [],
+ "details": "INV-G4",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "59"
+ },
+ {
+ "id": 4,
+ "title": "[GAP-05] איחוד staging/derivation/citation-guard/multimodal/fallback",
+ "description": "שאר 6 האסימטריות → פרמטרים של המסלול הקנוני (01-ingest §4).",
+ "dependencies": [],
+ "details": "INV-ING1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "59"
+ }
+ ],
+ "updatedAt": "2026-05-30T17:37:34.741136+00:00"
+ },
+ {
+ "id": "60",
+ "title": "[FU-2] ingest idempotent + מזהים קנוניים",
+ "description": "מפתח-upsert דטרמיניסטי + נרמול case_number בכתיבה + תיאום מספרים מעורבים.",
+ "details": "מכסה GAP-03,06,07,08,13. מספק INV-ING2/G3/G1/ID1/ID2/DM2/DM1. severity: Critical. סוג: קוד + מיגרציית-נתונים (דורש אישור-עלות). תלוי ב-FU-1.",
+ "testStrategy": "",
+ "status": "pending",
+ "dependencies": [
+ "59"
+ ],
+ "priority": "high",
+ "subtasks": [
+ {
+ "id": 1,
+ "title": "[GAP-03] קליטה idempotent (upsert על מפתח קנוני)",
+ "description": "קליטה חוזרת = עדכון, לא כפילות.",
+ "dependencies": [],
+ "details": "INV-ING2/G3",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "60"
+ },
+ {
+ "id": 2,
+ "title": "[GAP-06] נרמול case_number בכתיבה",
+ "description": "היום רק תיקון-קריאה (_normalize_case_number, db.py:1196-1211).",
+ "dependencies": [],
+ "details": "INV-G1/ID1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "60"
+ },
+ {
+ "id": 3,
+ "title": "[GAP-07] תיאום מספרי-תיק מעורבים (with-month canonical)",
+ "description": "מיגרציה חד-פעמית; הצורה עם-חודש קנונית (החלטת-יו\"ר).",
+ "dependencies": [],
+ "details": "INV-ID1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "60"
+ },
+ {
+ "id": 4,
+ "title": "[GAP-08] הסרת ציטוט-מלא כ-case_number",
+ "description": "רשומות עם ציטוט מלא כמזהה (legacy).",
+ "dependencies": [],
+ "details": "INV-DM2/ID2",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "60"
+ },
+ {
+ "id": 5,
+ "title": "[GAP-13] שדה searchable מפורש",
+ "description": "דגל 'עבר חוזה-שלמות' מובחן מ-extraction_status.",
+ "dependencies": [],
+ "details": "INV-DM1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "60"
+ }
+ ],
+ "updatedAt": "2026-05-30T17:37:34.741136+00:00"
+ },
+ {
+ "id": "61",
+ "title": "[FU-3] re-index בשינוי תוכן",
+ "description": "embedding מתעדכן אוטומטית בשינוי תוכן (כיום trigger-dependent, לא GENERATED).",
+ "details": "מכסה GAP-09. מספק INV-DM3/G6. severity: High. סוג: קוד + מיגרציה (re-embed). תלוי ב-FU-1.",
+ "testStrategy": "",
+ "status": "pending",
+ "dependencies": [
+ "59"
+ ],
+ "priority": "medium",
+ "subtasks": [
+ {
+ "id": 1,
+ "title": "[GAP-09] re-index/re-embed בשינוי תוכן",
+ "description": "embedding לא GENERATED בניגוד ל-tsvectors; נקודת-drift.",
+ "dependencies": [],
+ "details": "INV-DM3/G6",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "61"
+ }
+ ],
+ "updatedAt": "2026-05-30T17:37:34.741136+00:00"
+ },
+ {
+ "id": "62",
+ "title": "[FU-4] בידוד-קורפוס בכל מסלול query",
+ "description": "אכיפת source_kind בכל פילטר (כולל halacha_filters); חסימת חיפוש ללא תחום.",
+ "details": "מכסה GAP-10,12. מספק INV-RET1/G5. severity: Critical. סוג: קוד. ללא תלות — דחוף (דליפה פעילה).",
+ "testStrategy": "",
+ "status": "pending",
+ "dependencies": [],
+ "priority": "high",
+ "subtasks": [
+ {
+ "id": 1,
+ "title": "[GAP-10] סינון source_kind ב-halacha_filters (#56)",
+ "description": "db.py:3168/3401 — halacha_filters בלי source_kind בעוד chunk_filters עם.",
+ "dependencies": [],
+ "details": "INV-RET1/G5",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "62"
+ },
+ {
+ "id": 2,
+ "title": "[GAP-12] חסימת חיפוש ללא practice_area",
+ "description": "search_decisions מזהיר אך לא חוסם (search.py:45-49).",
+ "dependencies": [],
+ "details": "INV-RET/G5",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "62"
+ }
+ ],
+ "updatedAt": "2026-05-30T17:37:34.741136+00:00"
+ },
+ {
+ "id": "63",
+ "title": "[FU-5] eval-harness + נראות backlog",
+ "description": "מדידת precision/recall על gold-set + חשיפת backlog הלכות בבדיקת-בריאות.",
+ "details": "מכסה GAP-11,14. מספק INV-RET4/G8/QA1/G10. severity: High. סוג: קוד + החלטת-יו\"ר (בניית gold-set). תלוי ב-FU-2.",
+ "testStrategy": "",
+ "status": "pending",
+ "dependencies": [
+ "60"
+ ],
+ "priority": "medium",
+ "subtasks": [
+ {
+ "id": 1,
+ "title": "[GAP-11] eval-harness precision+recall + gold-set",
+ "description": "כיום רק telemetry.log_search_bg; איכות-אחזור לא נמדדת.",
+ "dependencies": [],
+ "details": "INV-RET4/G8",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "63"
+ },
+ {
+ "id": 2,
+ "title": "[GAP-14] נראות backlog הלכות",
+ "description": "ספירת pending_review בבדיקת-בריאות (10/19 התגלה במקרה).",
+ "dependencies": [],
+ "details": "INV-QA1/G10",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "63"
+ }
+ ],
+ "updatedAt": "2026-05-30T17:37:34.741136+00:00"
+ },
+ {
+ "id": "64",
+ "title": "[FU-6] שערי-QA נאכפים בקוד",
+ "description": "export חוסם בקוד על כשל-QA קריטי; תיקון neutral_background critical-but-passes.",
+ "details": "מכסה GAP-15,16. מספק INV-QA3/EX3/G10. severity: Critical. סוג: קוד. ללא תלות — מהיר.",
+ "testStrategy": "",
+ "status": "pending",
+ "dependencies": [],
+ "priority": "high",
+ "subtasks": [
+ {
+ "id": 1,
+ "title": "[GAP-15] export חוסם QA קריטי בקוד",
+ "description": "export_docx (drafting.py:384) לא קורא ל-validate_decision — ניתן לעקיפה.",
+ "dependencies": [],
+ "details": "INV-QA3/EX3",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "64"
+ },
+ {
+ "id": 2,
+ "title": "[GAP-16] תיקון neutral_background critical-but-passes",
+ "description": "fallback בלוק-ו ריק מסומן critical אך passed=True (qa_validator.py:70).",
+ "dependencies": [],
+ "details": "QA-gate",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "64"
+ }
+ ],
+ "updatedAt": "2026-05-30T17:37:34.741136+00:00"
+ },
+ {
+ "id": "65",
+ "title": "[FU-7] audit-trail + provenance",
+ "description": "כתיבת audit_log בכל פעולה; קישור בלוק→קטעי-מקור; סנכרון DB אחרי עריכה; אימות citation→corpus.",
+ "details": "מכסה GAP-17,18,19,20. מספק INV-AUD1/2/3/EX1/G9. severity: High. סוג: קוד + backfill קל. תלוי ב-FU-1. (זרע לתת-פרויקט 3/audit-provenance.)",
+ "testStrategy": "",
+ "status": "pending",
+ "dependencies": [
+ "59"
+ ],
+ "priority": "medium",
+ "subtasks": [
+ {
+ "id": 1,
+ "title": "[GAP-17] סנכרון בלוקי-DB אחרי עריכת draft",
+ "description": "active_draft_path הופך ל'מקור-אמת', בלוקים לא מסונכרנים (db.py:189).",
+ "dependencies": [],
+ "details": "INV-EX1/AUD2",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "65"
+ },
+ {
+ "id": 2,
+ "title": "[GAP-18] כתיבת audit_log בכל פעולה מהותית",
+ "description": "הטבלה קיימת אך נכתבת כמעט רק ב-case_subtype_override (cases.py:203).",
+ "dependencies": [],
+ "details": "INV-AUD1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "65"
+ },
+ {
+ "id": 3,
+ "title": "[GAP-19] קישור בלוק→קטעי-מקור",
+ "description": "decision_blocks שומר model_used אך לא אילו chunks/precedents הזינו.",
+ "dependencies": [],
+ "details": "INV-AUD1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "65"
+ },
+ {
+ "id": 4,
+ "title": "[GAP-20] אימות citation→corpus",
+ "description": "decision_paragraphs.citations ללא ולידציה שכל ציטוט מתאים.",
+ "dependencies": [],
+ "details": "INV-AUD3",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "65"
+ }
+ ],
+ "updatedAt": "2026-05-30T17:37:34.741136+00:00"
+ },
+ {
+ "id": "66",
+ "title": "[FU-8] מחסומי-תהליך → מחסומי-קוד",
+ "description": "אכיפת sync חוצה-חברות; מחסומי-קוד ל-wakeup/API; חיווט הספ לסוכנים.",
+ "details": "מכסה GAP-21,22,23. מספק INV-MC1/INT1/INT3/AG1. severity: High. סוג: קוד + החלטת-יו\"ר. GAP-23 prereq לתת-פרויקט 5.",
+ "testStrategy": "",
+ "status": "pending",
+ "dependencies": [],
+ "priority": "medium",
+ "subtasks": [
+ {
+ "id": 1,
+ "title": "[GAP-21] אכיפת sync חוצה-חברות",
+ "description": "sync ידני ולא-נאכף; adapter_type-mismatch מדולג בשקט (sync...py:387-389).",
+ "dependencies": [],
+ "details": "INV-MC1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "66"
+ },
+ {
+ "id": 2,
+ "title": "[GAP-22] מחסומי-קוד ל-wakeup/API",
+ "description": "אין אילוץ-schema נגד INSERT ישיר ל-agent_wakeup_requests; אין linter נגד httpx/curl גולמי.",
+ "dependencies": [],
+ "details": "INV-INT1/INT3",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "66"
+ },
+ {
+ "id": 3,
+ "title": "[GAP-23] חיווט הספ לסוכנים",
+ "description": "HEARTBEAT/agent docs דורשים קריאת 00-constitution + ספ-תחום (prereq לתת-פרויקט 5).",
+ "dependencies": [],
+ "details": "INV-AG1",
+ "status": "pending",
+ "testStrategy": "",
+ "parentId": "66"
+ }
+ ],
+ "updatedAt": "2026-05-30T17:37:34.741136+00:00"
}
],
"metadata": {
diff --git a/docs/spec/00-constitution.md b/docs/spec/00-constitution.md
new file mode 100644
index 0000000..98d72d5
--- /dev/null
+++ b/docs/spec/00-constitution.md
@@ -0,0 +1,276 @@
+# 00 — חוקת המערכת (Constitution)
+
+זהו שער-הכניסה היחיד לספ המערכת *עוזר משפטי*. הוא מגדיר את הייעוד, עקרונות-העבודה,
+תבנית ה-invariant, פרוטוקול-האימות, ה-invariants הגלובליים (G1–G11), כללי-ההנדסה,
+אינדקס הספ ונספח המקורות. כל קובץ-תחום (01–07, X1–X5) כפוף לחוקה זו ומפנה אליה.
+
+---
+
+## 1. ייעוד
+
+> מערכת AI שמסייעת ליו"ר ועדת הערר לתכנון ובנייה (מחוז ירושלים, עו"ד דפנה תמיר) לנסח
+> **החלטות מעין-שיפוטיות כתובות ומנומקות** — מסמכים משפטיים פורמליים שעומדים לביקורת
+> שיפוטית — תוך שמירה על **הקול, השיקול והאחריות של היו"ר**.
+
+- **משרת:** יו"ר הוועדה (משתמש-על) והסוכנים הפועלים בשמה.
+- **מחזור-חיים:** ניהול תיקים → בסיס ידע (3 קורפוסים) → אחזור סמנטי (RAG) → סיוע-כתיבה
+ (12 בלוקים, סגנון דפנה) → ייצוא DOCX.
+- **3 סוגי עררים:** רישוי ובנייה (1xxx, חם), היטל השבחה (8xxx, קר), פיצויים ס'197 (9xxx, קר).
+- **ה"למה" העמוק:** המערכת מסייעת — היו"ר מכריעה (שערים קריטיים ידניים בכוונה); מנוע
+ צבירת-ידע (לומד מהחלטות סופיות ומפידבק); רב-חברתי (CMP/CMPA).
+
+---
+
+## 2. עקרונות-עבודה
+
+1. **אסור להניח שהקיים תקין (בהנדסה).** כל מה שמופה בקוד = "טענה לבדיקה", לא "אמת".
+ "תקין" מבחינה הנדסית נגזר ממקורות חיצוניים סמכותיים, לא מהמערכת שתחת חשד.
+2. **פרוטוקול אימות 3-מקורות — חל על החלטות הנדסה/פיתוח בלבד:** כל invariant הנדסי/
+ ארכיטקטוני (תכנון ובניית האפליקציה — נתונים, מזהים, ingest, אחזור) מגובה ב-**≥3 מקורות
+ סמכותיים מוכרים** בעלי ידע מקצועי מוכח. כשאין 3 → מסומן `⚠ UNVERIFIED` ומועלה ליו"ר.
+ **התוכן המשפטי אינו כפוף לכלל זה** — הסמכות עליו היא היו"ר (דפנה) ומסמכי-הפרויקט
+ (block-schema, decision-methodology, legal-decision-lessons, skills/decision), לא
+ מקורות חיצוניים.
+3. **מנגנון:** מחקר עצמאי → טיוטה לביקורת. קודם חוקרים את הסמכויות החיצוניות (להחלטות
+ הנדסה), ורק אז מנסחים את ה-invariant.
+4. **מודל-שיתוף:** על החלטות טכניות/אדריכליות אני חוקר ומכריע מקצועית ומציג תוצאה
+ מוגמרת. שואל את היו"ר (חיים) רק במקום שבו *הוא* הסמכות — כוונה, עדיפויות עסקיות,
+ ותוכן משפטי-דומייני.
+
+---
+
+## 3. תבנית-invariant
+
+מבנה אחיד לכל חוק בספ (בכל הקבצים):
+
+```
+### INV-<תחום><מספר>: <כותרת קצרה>
+**כלל:** <ניסוח נורמטיבי חד — מה חייב להתקיים>
+**מקורות:** <≥3 סמכויות> | סטטוס: verified / ⚠ UNVERIFIED
+**אכיפה:** <היכן/איך נאכף — schema / ולידציית-כתיבה / בדיקת-בריאות / שער אנושי>
+**הפרה ידועה:** <דוגמה מהמערכת, אם יש — מקשר ל-audit; אחרת "—">
+```
+
+> **שדה המקורות לפי סוג invariant (שלושה מודלי-סמכות):**
+> 1. **הנדסי** (תאוריה כללית — נתונים/אחזור/ארכיטקטורה) → `מקורות` = ≥3 סמכויות חיצוניות + `סטטוס`.
+> 2. **תוכן-משפטי** → `מקור-סמכות` = היו"ר + מסמכי-הפרויקט (ללא סטטוס-אימות חיצוני).
+> 3. **פרויקטלי-תפעולי** (עובדות על האינטגרציה/התפעול של *מערכת זו* — אין להן סמכות
+> חיצונית, למשל "wakeup דרך API") → `מקור-סמכות` = ה-runbooks של הפרויקט
+> (CLAUDE.md, HEARTBEAT.md, סקריפטים), **קשור** ל-invariant הנדסי גלובלי שאותו הוא מיישם.
+
+---
+
+## 4. פרוטוקול-אימות
+
+> חל על **invariants הנדסיים (G1–G10)** — החלטות תכנון/בניית האפליקציה. ה-invariant של
+> תוכן-משפטי (G11) **אינו** כפוף לפרוטוקול זה; הסמכות עליו היא היו"ר + מסמכי-הפרויקט.
+
+- כל invariant הנדסי נושא שדה `מקורות` + `סטטוס: verified / ⚠ UNVERIFIED`.
+- **verified** = מגובה ב-**≥3 מקורות סמכותיים** מוכרים בעלי ידע מקצועי מוכח.
+- **⚠ UNVERIFIED** = החלטה הנדסית שיש לה פחות מ-3 מקורות סמכותיים מאומתים. פריט כזה
+ **לא מוכרע לבד** — מועלה ליו"ר עם הערת-הסלמה המתעדת מה חסר והיכן יאומת.
+- החלטות טכניות → מחקר עצמאי + הכרעה מקצועית + הצגת תוצאה. שאלה ליו"ר רק במקום
+ שבו הוא הסמכות (ראה עיקרון 4 לעיל).
+
+---
+
+## 5. Invariants גלובליים
+
+אלה החוקים החוצים את כל המערכת — לב החוקה. הם נחלקים לשני סוגים לפי **מקור-הסמכות**:
+
+- **G1–G10 — invariants הנדסיים** (תכנון/בניית האפליקציה): כל אחד מגובה ב-**≥3 סמכויות
+ טכניות מוכרות** (נספח §8). ביחד הם מייבשים את כשל-השורש החוזר: מסלולים/קורפוסים
+ מקבילים שמתפצלים (drift) בלי שכבה שמגדירה ואוכפת "תקין".
+- **G11 — invariant תוכן-משפטי:** הסמכות עליו היא **היו"ר (דפנה) + מסמכי-הפרויקט**, לא
+ מקורות חיצוניים, ואינו כפוף לפרוטוקול ≥3-המקורות.
+
+### 5א. Invariants הנדסיים (G1–G10)
+
+### INV-G1: מזהה קנוני מנורמל בכתיבה
+**כלל:** לכל ישות יש מזהה קנוני יחיד, **מנורמל בנקודת-הכתיבה** (לא תיקון-סלחני בקריאה
+בלבד). `case_number` נשמר בצורה קנונית אחת; קריאה משווה מול הצורה הקנונית, לא מטליאה.
+**מקורות:** SSOT (Single Source of Truth — normalization principle) · E.F. Codd, First
+Normal Form (CACM 13(6), 1970) · Martin Kleppmann, *Designing Data-Intensive Applications*
+(O'Reilly, 2017) | סטטוס: verified
+**אכיפה:** schema (אילוץ ייחודיות על המפתח הקנוני) + ולידציית-כתיבה בנקודת-הקליטה;
+מפורט ב-[X1-identifiers.md](X1-identifiers.md) ו-[02-data-model.md](02-data-model.md).
+**הפרה ידועה:** `_normalize_case_number` סלחני בקריאה בלבד (קומיט "tolerant case_number
+lookup"); `8126-25` לא נמצא מול האמיתי `8126-03-25` → ממצא ל-[audit](../audit-report.md).
+
+### INV-G2: מקור-אמת יחיד — אין מסלולים מקבילים מתפצלים
+**כלל:** לכל סוג-נתון יש **מקור-אמת יחיד** ומסלול-קוד קנוני אחד. אסור להוסיף מסלול
+מקביל ליכולת קיימת — ישויות-אחיות חולקות מסלול קנוני אחד; נתונים נגזרים (derived)
+משוחזרים מהמקור, לא נכתבים במקביל.
+**מקורות:** Martin Kleppmann (system of record vs. derived data, *DDIA* 2017) · Martin
+Fowler (Canonical Data Model) · SSOT (Single Source of Truth) | סטטוס: verified
+**אכיפה:** ביקורת-ארכיטקטורה + כלל-הנדסה "סימטריה" (§6); מפורט ב-[01-ingest.md](01-ingest.md).
+**הפרה ידועה:** שני מסלולי ingest מקבילים לישויות-אחיות (`ingest_precedent` מול
+`ingest_internal_decision`) שמתפצלים — לדוגמה: המסלול החיצוני מתזמן חילוץ metadata
+(`request_metadata_extraction`), והמסלול הפנימי לא — ולכן ערן סופר 8046/24 נקלטה בלי
+metadata → ממצא ל-[audit](../audit-report.md).
+
+### INV-G3: ingest אחיד ו-idempotent
+**כלל:** קליטה היא **אחידה ו-idempotent** — upsert על מפתח דטרמיניסטי. קליטה חוזרת של
+אותו פריט אינה יוצרת כפילות ואינה משנה תוצאה.
+**מקורות:** Martin Kleppmann (*DDIA*, idempotence & exactly-once) · Stripe / CDC
+idempotency-key pattern · ISO 8000 (Data quality) | סטטוס: verified
+**אכיפה:** ולידציית-כתיבה + מפתח-upsert דטרמיניסטי בנקודת-הקליטה; מפורט ב-
+[01-ingest.md](01-ingest.md).
+**הפרה ידועה:** 3 החלטות "סופר" נקלטו ב-3 פורמטים שונים (`8126/24`, ציטוט-מלא
+כ-case_number) — היעדר upsert דטרמיניסטי → ממצא ל-[audit](../audit-report.md).
+
+### INV-G4: חוזה-שלמות לפני "שמיש / ניתן-לחיפוש"
+**כלל:** רשומה אינה נחשבת "שמישה" או "ניתנת-לחיפוש" עד ש**שדות-החובה שלה מולאו ואומתו
+מול spec מפורש**. שלמות נבדקת לפני חשיפה לאחזור.
+**מקורות:** ISO 8000 (completeness) · DAMA-UK *Six Primary Dimensions for Data Quality*
+(2013, completeness) · ISO 15489-1:2016 (records reliability) | סטטוס: verified
+**אכיפה:** חוזה-שלמות באכיפת-קוד + בדיקת-בריאות; מפורט ב-[02-data-model.md](02-data-model.md)
+ו-[03-retrieval.md](03-retrieval.md).
+**הפרה ידועה:** ערן סופר 8046/24 אונדקס עם `headnote`/`summary`/`tags` ריקים → ממצא
+ל-[audit](../audit-report.md).
+
+### INV-G5: metadata מלא + הפרדת-קורפוס נאכפת בכל query
+**כלל:** לכל פריט מואנדקס יש **metadata מלא** (כולל מזהה-מקור וסוג-קורפוס), ו**הפרדת-
+הקורפוס נאכפת בכל מסלול-query** — אין דליפה בין 3 הקורפוסים.
+**מקורות:** Pinecone (multitenancy / metadata filtering) · RAG attribution (Lewis et al.,
+2020, NeurIPS) · ISO 8000 (Data quality) | סטטוס: verified
+**אכיפה:** schema (metadata חובה) + פילטר-קורפוס נאכף בשכבת-החיפוש; מפורט ב-
+[03-retrieval.md](03-retrieval.md) ו-[X5-audit-provenance.md](X5-audit-provenance.md).
+**הפרה ידועה:** משימה #56 — דליפת `source_kind` ב-`halacha_filters` בין קורפוסים →
+ממצא ל-[audit](../audit-report.md).
+
+### INV-G6: re-index בכל שינוי תוכן
+**כלל:** כל שינוי-תוכן של פריט מואנדקס מפעיל **re-index** של ה-embedding שלו. אין
+embeddings מיושנים מול התוכן הנוכחי.
+**מקורות:** Pinecone (index freshness / data sync) · Weaviate (re-vectorization on update)
+· RAG freshness (Lewis et al., 2020) | סטטוס: verified
+**אכיפה:** טריגר re-index בנקודת-העדכון + בדיקת-בריאות (גילוי drift); מפורט ב-
+[02-data-model.md](02-data-model.md) ו-[03-retrieval.md](03-retrieval.md).
+**הפרה ידועה:** —
+
+### INV-G7: מיזוג RRF — לא סכום-ציונים
+**כלל:** מיזוג תוצאות בין retrievers נעשה **לפי דירוג (Reciprocal Rank Fusion)**, לא
+סכום/ממוצע ציונים גולמיים — שכן ציונים בסקיילים שונים אינם בני-השוואה ישירה.
+**מקורות:** Elastic (*Reciprocal Rank Fusion*) · Weaviate (*Hybrid Search Explained*) ·
+OpenSearch / Azure AI Search (corroborating RRF guidance) | סטטוס: verified
+**אכיפה:** קוד-המיזוג בשכבת-האחזור; מפורט ב-[03-retrieval.md](03-retrieval.md).
+**הפרה ידועה:** —
+
+### INV-G8: איכות-אחזור נמדדת — precision + recall
+**כלל:** איכות-האחזור **נמדדת אמפירית (precision + recall)** באמצעות eval harness, לא
+מונחת. שינוי בשכבת-האחזור מלווה במדידה.
+**מקורות:** Manning, Raghavan & Schütze, *Introduction to Information Retrieval* (CUP,
+2008) · RAG evaluation literature (Lewis et al., 2020 ואחריו) · Elastic (relevance
+evaluation guidance) | סטטוס: verified
+**אכיפה:** eval harness + בדיקת-בריאות תקופתית; מפורט ב-[03-retrieval.md](03-retrieval.md).
+**הפרה ידועה:** —
+
+### INV-G9: עקיבוּת-מקור + audit-trail ל-AI
+**כלל:** כל פלט של המערכת **עקיב למקורו** (citation/provenance), וכל שימוש ב-AI מתועד
+ב-**audit-trail** הניתן לביקורת.
+**מקורות:** Council of Europe / CEPEJ — *European Ethical Charter on AI in judicial systems*
+(2018, user-control principle) · NCSC/JTC — *Principles & Practices for AI Use in Courts* ·
+ISO 15489-1:2016 (records authenticity/integrity) | סטטוס: verified
+**אכיפה:** audit-trail באכיפת-קוד + עקיבוּת-מקור בכל פלט; מפורט ב-
+[X5-audit-provenance.md](X5-audit-provenance.md).
+**הפרה ידועה:** —
+
+### INV-G10: המערכת מסייעת — שערים אנושיים הם invariant
+**כלל:** המערכת **מסייעת ואינה מחליפה את שיקול-הדעת האנושי**. השערים האנושיים (אישור
+הלכה, בחירת תוצאה, פידבק היו"ר) הם **invariant — חובה, לא רשות**.
+**מקורות:** NCSC/JTC — *Principles & Practices for AI Use in Courts* ("never replace human
+judgment") · CEPEJ (2018, under user control) · Federal Judicial Center — *Judicial Writing
+Manual* (2d ed.) | סטטוס: verified
+**אכיפה:** שערים אנושיים בקוד-הזרימה (gate לא ניתן לעקיפה); מפורט ב-[05-qa-review.md](05-qa-review.md).
+**הפרה ידועה:** 10/19 הלכות מאושרות, התגלה במקרה — שער ידני שקוף בלי נראות backlog →
+ממצא ל-[audit](../audit-report.md).
+
+### 5ב. Invariant תוכן-משפטי (G11)
+
+### INV-G11: תוכן החלטה מנומקת
+**כלל:** החלטה מנומקת מקיימת: **רקע ניטרלי** (עובדות בלבד, ללא שיפוט) · **ללא כפילות**
+(בלוק דיון מפנה, לא חוזר) · **מענה לטענות הצד המפסיד** · **"מבחן-השופט"** (קריא לשופט שלא
+מכיר את התיק) · **טענות מקוריות בלבד** (מכתבי הטענות).
+**מקור-סמכות:** היו"ר (עו"ד דפנה תמיר) + מסמכי-הפרויקט — [block-schema.md](../block-schema.md),
+[decision-methodology.md](../decision-methodology.md), [legal-decision-lessons.md](../legal-decision-lessons.md),
+[skills/decision/SKILL.md](../../skills/decision/SKILL.md). **אינו כפוף לפרוטוקול ≥3-המקורות החיצוני** —
+זהו תוכן משפטי-דומייני, באחריות היו"ר.
+**אכיפה:** שערי QA + checklist-תוכן לפי סוג-ערר; מפורט ב-[04-analysis-writing.md](04-analysis-writing.md)
+ו-[05-qa-review.md](05-qa-review.md).
+**הפרה ידועה:** —
+
+---
+
+## 6. כללי-הנדסה (מונעים הישנות)
+
+- **סימטריה:** אסור להוסיף מסלול מקביל ליכולת קיימת — מרחיבים את המסלול הקנוני
+ (נגזר מ-[G2](#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)).
+- **נרמול לא תיקון-תסמין:** מתקנים נתון במקור (קנוני), לא מטליאים בקריאה
+ (נגזר מ-[G1](#inv-g1-מזהה-קנוני-מנורמל-בכתיבה)).
+- **Quality-at-source:** שלמות נאכפת קרוב ככל האפשר לקליטה (Martin Fowler — Data Mesh /
+ quality-at-source; נגזר מ-[G4](#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)).
+- **אין בליעה שקטה:** רשומה חסרה/פגומה מסומנת ומדווחת, לא מתקבלת בשקט (תואם feedback
+ קיים — אסור bare `except: pass`; נגזר מ-[G4](#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)).
+
+---
+
+## 7. אינדקס הספ
+
+> הערה: כל קבצי הספ (00, 01–07, X1–X5) קיימים. החוקה היא שער-הכניסה; כל קובץ-תחום כפוף לה.
+
+| קובץ | תפקיד | אוכף invariants |
+|------|--------|-----------------|
+| [00-constitution.md](00-constitution.md) | חוקה — ייעוד, invariants גלובליים, כללי-הנדסה, אינדקס | G1–G11 |
+| [01-ingest.md](01-ingest.md) | קליטה מאוחדת: מסמכי-תיק / פסיקה חיצונית / החלטות-ועדה — חוזה מסלול-יחיד | G2, G3 |
+| [02-data-model.md](02-data-model.md) | אחסון: ישויות (cases, case_law, documents, chunks, halachot…) + חוזה-שלמות לכל ישות | G1, G4, G6 |
+| [03-retrieval.md](03-retrieval.md) | 3 קורפוסים + כלי-חיפוש · hybrid/RRF · attribution · eval harness | G4, G5, G6, G7, G8, G9 |
+| [04-analysis-writing.md](04-analysis-writing.md) | חילוץ טענות · 12 בלוקים · סגנון דפנה (מצטט block-schema.md) | G11 |
+| [05-qa-review.md](05-qa-review.md) | שערי QA + שערים אנושיים (אישור הלכה, בחירת תוצאה, פידבק) כ-invariant | G10, G11 |
+| [06-export.md](06-export.md) | ייצוא DOCX לפי תבנית דפנה | G2, G9 |
+| [07-learning.md](07-learning.md) | Hermes · לקחים · לולאת פידבק היו"ר · צמיחת קורפוס (quality-at-source) | G4, G10 |
+| [X1-identifiers.md](X1-identifiers.md) | מודל מזהים קנוני: נרמול case_number בכתיבה · cases מול case_law · פורמטי ציטוט | G1 |
+| [X2-multi-company.md](X2-multi-company.md) | CMP/CMPA · 14 סוכנים · כללי sync | G2 |
+| [X3-integration-deploy.md](X3-integration-deploy.md) | Paperclip (wakeup, ניתוב comments, webhooks) · Coolify/pm2 | G2, G9 (תפעולי) |
+| [X4-agents.md](X4-agents.md) | מפת הסוכנים (דומיין + סוכני-התהליך) | G10 |
+| [X5-audit-provenance.md](X5-audit-provenance.md) | audit-trail לשימוש ב-AI · עקיבוּת כל מקור מצוטט · שלמות-רשומה | G5, G9 |
+
+**עקרונות:** כל קובץ עצמאי, ממוקד, agent-readable, יעד ≤~500 שורות (תפיחה = סימן
+לפיצול). מסמכים קיימים (`architecture.md`, `product-specification.md`, `block-schema.md`…)
+לא נמחקים ולא משוכפלים — מצוטטים כ"מקור" ומאומתים מול הסמכויות; סתירה = ממצא ל-audit.
+
+---
+
+## 8. נספח מקורות סמכותיים
+
+(מאומתים במחקר 30.5.2026)
+
+**ממשל-AI שיפוטי + שערים אנושיים (G9, G10)**
+- NCSC / JTC — *Court Technology Standards* + *Principles & Practices for AI Use in Courts*.
+ https://www.ncsc.org/our-centers-projects/joint-technology-committee/court-technology-standards
+- Council of Europe / CEPEJ — *European Ethical Charter on the use of AI in judicial
+ systems* (2018, user-control principle).
+- Federal Judicial Center — *Judicial Writing Manual* (2d ed.) — לעניין שיקול-הדעת
+ האנושי בכתיבה השיפוטית.
+ https://www.fjc.gov/content/judicial-writing-manual-pocket-guide-judges-second-edition
+
+**אחזור / RAG / IR**
+- Lewis et al. (2020) — *Retrieval-Augmented Generation* (NeurIPS).
+ https://arxiv.org/abs/2005.11401
+- Manning, Raghavan & Schütze — *Introduction to Information Retrieval* (CUP, 2008).
+ https://nlp.stanford.edu/IR-book/
+- Elastic — *Reciprocal Rank Fusion*.
+ https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion
+- Pinecone — *Implement multitenancy*.
+ https://docs.pinecone.io/guides/index-data/implement-multitenancy
+- Weaviate — *Hybrid Search Explained*. https://weaviate.io/blog/hybrid-search-explained
+
+**שלמות-נתונים / איכות / רשומות**
+- DAMA-DMBOK2 + DAMA-UK — *Six Primary Dimensions for Data Quality* (2013).
+- ISO 8000 — Data quality (8000-8/61/110).
+- ISO 15489-1:2016 — Records management (authenticity/reliability/integrity/usability).
+- Martin Kleppmann — *Designing Data-Intensive Applications* (O'Reilly, 2017).
+- E.F. Codd — Relational model & normalization (CACM 13(6), 1970).
+- Martin Fowler — Canonical Data Model / Data Mesh (quality-at-source).
+
+(נספח המקורות מתייחס ל-invariants ההנדסיים G1–G10 בלבד. התוכן המשפטי — G11 — נשען על
+מסמכי-הפרויקט וסמכות היו"ר, כמפורט ב-G11.)
diff --git a/docs/spec/01-ingest.md b/docs/spec/01-ingest.md
new file mode 100644
index 0000000..898cdcc
--- /dev/null
+++ b/docs/spec/01-ingest.md
@@ -0,0 +1,150 @@
+# 01 — קליטה מאוחדת (Unified Ingest Contract)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומפרט את **חוזה הקליטה** של כל סוגי
+ה-intake. הוא אוכף את [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
+(מקור-אמת יחיד, אין מסלולים מקבילים) ואת [G3](00-constitution.md#inv-g3-ingest-אחיד-ו-idempotent)
+(ingest אחיד ו-idempotent), ונשען על [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)
+ו-[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן).
+
+כשל-השורש שהקובץ מייבש: **שני מסלולי ingest לישויות-אחיות שמתפצלים** — `ingest_precedent`
+(פסיקה חיצונית) מול `ingest_internal_decision` (החלטות-ועדה). מסלולים מקבילים גוררים drift:
+פריט שנקלט במסלול אחד מקבל טיפול שונה מפריט במסלול האחר, והפער מתגלה רק כשרשומה חסרה
+metadata או לא נמצאת בחיפוש. החוזה כאן מגדיר **מסלול קנוני אחד** ש-3 סוגי ה-intake עוברים בו.
+
+---
+
+## 1. שלושת סוגי ה-intake
+
+| סוג-intake | מזהה-קנוני | קורפוס-יעד | מאפיין ייחודי |
+|------------|------------|------------|----------------|
+| מסמכי-תיק (case documents) | `case_number` + מזהה-מסמך | תיק ערר פעיל | משויך לתיק, מסווג לפי סוג-מסמך |
+| פסיקה חיצונית (external precedent) | `citation` (קנוני) | `case_law` (external) | staging לפי `source_type`, ולידציית-enums, citation guard, multimodal |
+| החלטות-ועדה (internal-committee) | `case_number` (קנוני) | `case_law` (internal_committee) | staging לפי district, `chair_name` חובה, גזירת district/proceeding_type |
+
+שלושתם הם **ישויות-אחיות**: אותו טיפוס-עיבוד (קובץ → טקסט → chunks → embeddings → metadata
+→ הלכות), נבדלים בפרמטרים בלבד — לא במסלול-קוד. זוהי משמעות "סימטריה" (חוקה §6).
+
+---
+
+## 2. המסלול הקנוני (Canonical Pipeline)
+
+צעדי-העיבוד, **בסדר מחייב**. כל סוג-intake עובר את אותם צעדים; ההבדל הוא אילו פרמטרים
+מוזרקים בקלט, לא אילו צעדים מורצים.
+
+1. **Stage file** — העתקה דטרמיניסטית לאחסון המתמיד. נתיב-ה-staging הוא פרמטר
+ (`source_type` לפסיקה חיצונית, district להחלטות-ועדה), לא ענף-קוד נפרד.
+2. **Extract text** — `extractor.extract_text` → `(text, page_count, page_offsets)`.
+ טקסט ריק = כשל מדווח (לא בליעה שקטה; חוקה §6).
+3. **Strip Nevo preamble** — `extractor.strip_nevo_preamble` להסרת עטיפת-Nevo. **אחיד לכל סוג.**
+4. **Chunk** — היררכי (`chunk_document_hierarchical`) אם `PARENT_DOC_RETRIEVAL_ENABLED`,
+ אחרת שטוח (`chunk_document`). **אותו ענף-flag בדיוק לכל סוג** — בורר הצ'אנקינג נגזר
+ מ-config, לא מסוג-ה-intake.
+5. **Embed** — `embeddings.embed_texts(..., input_type="document")` ל-children (היררכי)
+ או לכל ה-chunks (שטוח).
+6. **Store chunks** — `store_precedent_chunks_hierarchical` או `store_precedent_chunks`.
+7. **Page-image embed (multimodal)** — אם `MULTIMODAL_ENABLED` **וגם** הקובץ PDF
+ **וגם** `page_count>0`: הטמעת עמודי-תמונה (`_embed_precedent_pages`). non-fatal:
+ מסלול-הטקסט כבר הצליח. **התנאי אחיד** — הפעלה תלויה ב-flag+סוג-קובץ, לא בסוג-ה-intake.
+8. **Queue metadata extraction** — `request_metadata_extraction(case_law_id)`. נדרש לכל
+ סוג שתומך במטא-דאטה (ראה [INV-ING3](#inv-ing3-תור-חילוץ-מטא-דאטה--הלכות-לכל-סוג)).
+9. **Queue halacha extraction** — `request_halacha_extraction(case_law_id)`.
+10. **Set statuses** — `extraction_status=completed`, `halacha_status=pending`.
+ החילוץ ה-LLM-י (metadata + הלכות) רץ בנפרד מ-Claude Code המקומי
+ (`precedent_process_pending`), כי `claude` CLI אינו זמין בקונטיינר.
+
+> **צעדים שחייבים להיות אחידים בכל סוג (תיקון האסימטריה):** 2 (extract), 3 (strip-Nevo),
+> 4 (בורר-chunk לפי flag), 5–6 (embed+store), **7 (multimodal — לפי flag+PDF, לא לפי
+> סוג)**, **8–9 (תיזמון שני החילוצים)**, 10 (statuses). מה ש**רשאי** להשתנות לפי סוג:
+> נתיב-ה-staging (צעד 1), ולידציות-קלט ספציפיות, וגזירת-שדות (district/proceeding_type)
+> — אלו פרמטרים של אותו מסלול, לא מסלול נפרד.
+
+---
+
+## 3. Invariants של התחום
+
+### INV-ING1: מסלול-קליטה קנוני יחיד
+**כלל:** כל סוגי ה-intake (מסמכי-תיק / פסיקה חיצונית / החלטות-ועדה) זורמים דרך **פונקציית-
+קליטה קנונית אחת**. סוג-intake חדש מורחב דרך **פרמטרים** של אותה פונקציה — לעולם לא דרך
+פונקציה מקבילה. נתון-נגזר (district, proceeding_type) מחושב בתוך המסלול, לא בענף נפרד.
+**מקורות:** Martin Kleppmann, *DDIA* (O'Reilly, 2017 — system of record יחיד) · Martin
+Fowler (*Canonical Data Model*) · SSOT (Single Source of Truth) | סטטוס: verified
+**אכיפה:** ביקורת-ארכיטקטורה + כלל-הנדסה "סימטריה" (חוקה §6); הקליטה מתנקזת לפונקציה אחת
+שמקבלת פרמטרי-סוג. אוכף את [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים).
+**הפרה ידועה:** היום קיימים **שני** מסלולים — `ingest_precedent`
+(`precedent_library.py:88`) ו-`ingest_internal_decision` (`internal_decisions.py:73`) —
+שמשכפלים את צעדי 2–10 ומתפצלים בפרטים → ממצא ל-[audit](../audit-report.md).
+
+### INV-ING2: קליטה idempotent על המזהה הקנוני
+**כלל:** הקליטה היא **idempotent על המזהה הקנוני** (`citation` לפסיקה חיצונית,
+`case_number` להחלטות-ועדה ולמסמכי-תיק). קליטה חוזרת של אותו פריט = **upsert** —
+אין רשומה כפולה ואין chunks כפולים; התוצאה זהה.
+**מקורות:** Martin Kleppmann, *DDIA* (idempotence & exactly-once) · Stripe / CDC
+idempotency-key pattern · ISO 8000 (Data quality) | סטטוס: verified
+**אכיפה:** מפתח-upsert דטרמיניסטי על המזהה הקנוני בנקודת-הקליטה (`create_external_case_law`
+/ `create_internal_committee_decision`) + ולידציית-כתיבה; קשור ל-
+[X1-identifiers.md](X1-identifiers.md) (נרמול בכתיבה). אוכף את
+[G3](00-constitution.md#inv-g3-ingest-אחיד-ו-idempotent).
+**הפרה ידועה:** 3 החלטות "סופר" נקלטו ב-3 פורמטים (`8126/24`, ציטוט-מלא כ-`case_number`)
+— היעדר מפתח-upsert דטרמיניסטי גרר רשומות-כפל במקום עדכון → ממצא ל-[audit](../audit-report.md).
+
+### INV-ING3: תור חילוץ מטא-דאטה + הלכות לכל סוג
+**כלל:** חילוץ-מטא-דאטה **וגם** חילוץ-הלכות מתוזמנים (queue) עבור **כל** סוג-intake שתומך
+בהם — תיזמון אחיד, **לא** מותנה במסלול. שני התורים נפתחים יחד בסיום העיבוד הלא-LLM-י.
+**מקורות:** ISO 8000 (completeness) · DAMA-UK *Six Primary Dimensions for Data Quality*
+(2013, completeness) · Martin Fowler (quality-at-source) | סטטוס: verified
+**אכיפה:** קריאה ל-`request_metadata_extraction` **ו**-`request_halacha_extraction`
+בנקודת-סיום-הקליטה, לכל סוג; חוזה-שלמות יסמן רשומה ללא מטא-דאטה כלא-שמישה
+([G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש), מפורט ב-
+[02-data-model.md](02-data-model.md)).
+**הפרה ידועה:** המסלול הפנימי (`internal_decisions.py:208`) מתזמן **רק**
+`request_halacha_extraction` ואינו קורא ל-`request_metadata_extraction` (בניגוד
+ל-`precedent_library.py:292-293` שקורא לשניהם) → ערן סופר 8046/24 נקלטה **בלי
+metadata** (headnote/summary/tags ריקים) → ממצא ל-[audit](../audit-report.md).
+
+### INV-ING4: re-index בקליטה-חוזרת (upsert ⇒ re-embed)
+**כלל:** קליטה-חוזרת ששינתה את תוכן-הפריט מפעילה **re-index** — chunks ו-embeddings
+ישנים נמחקים ונבנים מחדש מהתוכן החדש. אין embeddings מיושנים אחרי upsert.
+**מקורות:** Pinecone (index freshness / data sync) · Weaviate (re-vectorization on update)
+· RAG freshness (Lewis et al., 2020, NeurIPS) | סטטוס: verified
+**אכיפה:** טריגר re-embed בנתיב ה-upsert של הקליטה + בדיקת-בריאות לגילוי drift; מפורט
+ב-[02-data-model.md](02-data-model.md) ו-[03-retrieval.md](03-retrieval.md). אוכף את
+[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן).
+**הפרה ידועה:** —
+
+---
+
+## 4. מצב קיים מול יעד — audit-findings
+
+הסעיף מתעד את ההבדלים בין שני המסלולים הקיימים. **אלו תסמינים לאיחוד תחת המסלול הקנוני,
+לא התנהגויות תקינות.** כל פריט אומת מול הקוד בפועל.
+
+- **חילוץ מטא-דאטה חסר במסלול הפנימי.** ראה [INV-ING3](#inv-ing3-תור-חילוץ-מטא-דאטה--הלכות-לכל-סוג)
+ (ההפרה המתועדת שם — ערן סופר 8046/24). **יעד:** צעד 8 (תור חילוץ) אחיד לשני הסוגים.
+- **ולידציית-enums א-סימטרית.** המסלול החיצוני מוודא `practice_area`/`source_type` מול
+ רשימות חוקיות (`precedent_library.py:131-134`); המסלול הפנימי **אינו** מוודא enums.
+ **יעד:** ולידציה אחידה בנקודת-הקליטה (חוזה-שלמות, [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)).
+- **staging מפוצל.** החיצוני עושה stage לפי `source_type` (`precedent_library.py:138`);
+ הפנימי עושה stage לפי district (`internal_decisions.py:113-115`). **יעד:** נתיב-staging
+ כפרמטר של המסלול הקנוני (צעד 1), לא ענף-קוד.
+- **גזירת-שדות רק במסלול הפנימי.** הפנימי גוזר district מ-court (`:104`) ו-proceeding_type
+ מ-appeal_subtype/case_name (`:105`), ודורש `chair_name` (`:134`). החיצוני אינו גוזר אלו.
+ **יעד:** גזירה כפרמטר אופציונלי של המסלול הקנוני (שדות-סוג, לא מסלול-סוג).
+- **citation guard רק במסלול החיצוני.** החיצוני חוסם ציטוט שמתחיל ב-`ערר`/`בל"מ`
+ ומפנה למסלול הפנימי (`precedent_library.py:124-130`). היעד שומר על השער הזה כניתוב-סוג
+ בתוך המסלול הקנוני, לא כהפרדת-פונקציות.
+- **multimodal page-image embed רק במסלול החיצוני.** החיצוני מטמיע עמודי-תמונה כש-
+ `MULTIMODAL_ENABLED` + PDF (`precedent_library.py:272-278`); הפנימי **אינו** מטמיע
+ עמודי-תמונה. **יעד:** צעד 7 אחיד — מותנה ב-flag+סוג-קובץ בלבד.
+- **fallback `case_name→citation` רק במסלול החיצוני.** החיצוני נופל ל-`citation` כשם
+ כשחסר `case_name` (`precedent_library.py:158`); הפנימי נופל ל-`case_number`
+ (`internal_decisions.py:130`). **יעד:** מדיניות-fallback אחת לשם-תצוגה במסלול הקנוני.
+
+---
+
+## 5. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md) — invariants גלובליים + כללי-הנדסה.
+- [02-data-model.md](02-data-model.md) — סכמת-האחסון + חוזה-שלמות שאוכף את תוצרי הקליטה.
+- [03-retrieval.md](03-retrieval.md) — אחזור, re-index, eval — היעד של ה-chunks הנקלטים.
+- [X1-identifiers.md](X1-identifiers.md) — נרמול המזהה הקנוני בכתיבה (בסיס ל-INV-ING2).
+- [X5-audit-provenance.md](X5-audit-provenance.md) — שלמות-רשומה + עקיבוּת-מקור של פריט נקלט.
diff --git a/docs/spec/02-data-model.md b/docs/spec/02-data-model.md
new file mode 100644
index 0000000..c8c3e9e
--- /dev/null
+++ b/docs/spec/02-data-model.md
@@ -0,0 +1,155 @@
+# 02 — מודל-הנתונים (Data Model & Completeness Contract)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומגדיר את **מודל-הנתונים הקנוני (TARGET)**
+של עוזר משפטי — הישויות, שדות-המפתח, והיכן יושב כל פריט מואנדקס. הוא אוכף את
+[G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה) (מזהה קנוני יחיד),
+[G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש) (חוזה-שלמות) ו-
+[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן) (re-index בשינוי-תוכן).
+
+> **TARGET, לא תיאור-מצב.** המודל כאן הוא היעד הקנוני. כל מקום שבו ה-schema בפועל
+> (`mcp-server/src/legal_mcp/services/db.py`) סוטה ממנו — מתועד כ-**audit-finding** (§4),
+> תסמין לאיחוד, לא התנהגות תקינה. כל טענה על ה-schema הקיים מצוטטת `file:line`.
+
+---
+
+## 1. הישויות הקנוניות
+
+הטבלה מונה את ישויות-הליבה. "מזהה-קנוני" = השדה היחיד המזהה רשומה ([G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה)).
+
+| ישות | תפקיד | מזהה-קנוני | שדות-מפתח (מאומתים `db.py`) |
+|------|--------|-------------|------------------------------|
+| `cases` | תיק ערר חי (1xxx/8xxx/9xxx) | `case_number` + `proceeding_type` | `title`, `status`, `practice_area`, `appeal_subtype`, `proceeding_type`, `chair_name` (`db.py:74-91,182-189,747,912`) |
+| `documents` | מסמך-מקור משויך לתיק | `id` (UUID); FK→`cases` | `doc_type`, `title`, `file_path`, `extracted_text`, `extraction_status`, `page_count` (`db.py:93-104`) |
+| `document_chunks` | chunk של מסמך-תיק + embedding | `id`; FK→`documents`/`cases` | `chunk_index`, `content`, `section_type`, `embedding vector(1024)`, `page_number` (`db.py:106-116`) |
+| `case_law` | קורפוס פסיקה — חיצוני **וגם** החלטות-ועדה | ראה [§2 + INV-DM2](#inv-dm2-מזהה-קנוני-יחיד-לכל-ישות) | `case_name`, `court`, `practice_area`, `source_kind`, `proceeding_type`, `source_type`, `headnote`, `summary`, `subject_tags`, `extraction_status`, `halacha_extraction_status` (`db.py:366-378,522-526,599-611,883,907`) |
+| `precedent_chunks` | chunk של פסק-דין מואנדקס (`source_kind='external_upload'`/`internal_committee`) | `id`; FK→`case_law` | `chunk_index`, `content`, `section_type`, `page_number`, `embedding vector(1024)`, `content_tsv` (`db.py:624-634,776`) |
+| `halachot` | הלכה מחולצת — כלל + ציטוט מילולי | `id`; FK→`case_law` | `rule_statement`, `supporting_quote`, `rule_type`, `practice_areas`, `subject_tags`, `confidence`, `quote_verified`, `review_status`, `embedding`, `rule_tsv` (`db.py:644-666,780`) |
+| `decisions` | החלטת-תיק מנוסחת (גרסה) | `id`; `UNIQUE(case_id, version)` | `version`, `status`, `outcome`, `outcome_summary` (`db.py:299-314`) |
+| `decision_blocks` | בלוק (12) של החלטה | `id`; `UNIQUE(decision_id, block_id)` | `block_id`, `block_index`, `content`, `status` (`db.py:317-334`) |
+| `claims` | טענת-צד (בלוק ז) | `id`; FK→`cases` | `party_role`, `claim_text`, `source_document`, `claim_type`, `claim_handling` (`db.py:349-359,506-512`) |
+| `chair_feedback` | הערת-יו"ר על טיוטה | `id`; FK→`cases` | `block_id`, `feedback_text`, `category`, `lesson_extracted`, `resolved` (`db.py:452-462`) |
+| `missing_precedents` | תקדים חסר שהתבקש ולא נמצא | `id` | (`db.py:806`) — backlog ל-quality-at-source |
+| `style_corpus` | קורפוס-סגנון של דפנה (אימון) | `id`; FK→`documents` | `decision_number`, `full_text`, `practice_area`, `appeal_subtype` (`db.py:118-131`) |
+
+> שכבות-עזר נוספות (`document_image_embeddings`, `precedent_image_embeddings` — multimodal,
+> `db.py:707,726`; `case_law_relations` — שרשרת-תיק, `db.py:754`; `precedent_internal_citations`
+> — גרף-ציטוטים, `db.py:937`) הן נגזרות ([G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)):
+> משוחזרות מהמקור, לא מקור-אמת עצמאי.
+
+---
+
+## 2. חוזה-שלמות לכל ישות (Completeness Contract)
+
+[G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש) דורש: **רשומה אינה "שמישה /
+ניתנת-לחיפוש" עד ששדות-החובה שלה מולאו ואומתו מול spec מפורש.** כל ישות מגדירה שתי רמות —
+**usable** (קיימת ומזוהה) ו-**searchable** (חשופה לאחזור). רשומה שנכשלת בחוזה **מסומנת
+ומדווחת — לא מתקבלת בשקט** (חוקה §6, "אין בליעה שקטה").
+
+### 2א. `case_law` — החוזה הקונקרטי
+
+המזהה הקנוני אינו `case_number` לבדו: `case_law` נושאת **שני** unique partial indexes לפי
+`source_kind` (`db.py:904-909`) — חיצוני: `UNIQUE(case_number)`; פנימי: `UNIQUE(case_number,
+proceeding_type)`. לכן המזהה הקנוני הוא **(`case_number` מנורמל, `source_kind`,
+`proceeding_type`)**.
+
+**רמת usable** (רשומה לגיטימית):
+- `case_number` קנוני מנורמל-בכתיבה ([INV-DM2](#inv-dm2-מזהה-קנוני-יחיד-לכל-ישות) — **לא** ציטוט-מלא)
+- `case_name` לא-ריק (לא fallback לציטוט/למספר)
+- `court` לא-ריק
+- `practice_area ∈ {rishuy_uvniya, betterment_levy, compensation_197}` (אכוף ב-CHECK, `db.py:614-617`)
+- `source_kind` מהמילון (`external_upload` / `cited_only` / `internal_committee` / `nevo_seed`) (`db.py:599-601`, `internal_decisions.py:4`)
+- `proceeding_type ∈ {ערר, בל"מ}` כשפנימי (אכוף ב-CHECK, `db.py:897-899`)
+
+**רמת searchable** (חשוף לאחזור — מעבר ל-usable):
+- **≥1 `precedent_chunk`** עם `embedding` לא-NULL (אחרת אין מה לאחזר סמנטית)
+- **metadata לא-ריק:** לפחות אחד מ-`headnote` / `summary` / `subject_tags` מלא — אלו השדות
+ ש-search מציג ומסנן לפיהם
+- `extraction_status = completed` (מטא-דאטה הושלם, `db.py:603`)
+
+**אכיפה מפורשת:** רשומה שעוברת usable אך נכשלת ב-searchable — **מסומנת `searchable=false`
+ולא מוחזרת מ-search**, ומופיעה ב-health-check כ-backlog. היא **אינה מתקבלת בשקט** כ"זמינה".
+
+### 2ב. חוזה תמציתי לישויות נוספות
+
+- `documents` → usable: `file_path`+`doc_type`; searchable: `extraction_status=completed` ו-`extracted_text` לא-ריק ו-≥1 `document_chunk` עם embedding.
+- `halachot` → usable: `rule_statement`+`supporting_quote`; **searchable: `review_status ∈ {approved, published}` בלבד** — `pending_review`/`rejected` מוסתרות מ-`search_precedent_library` (שער-הלכה ידני, `db.py:644-660`, [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)).
+- `decision_blocks` → usable: `block_id`∈12-הבלוקים; "מוכן": `status=final` ו-`content` לא-ריק.
+- `chair_feedback` → usable: `feedback_text`+`category` מהמילון; "פתוח" עד `resolved=true`.
+
+---
+
+## 3. Invariants של התחום
+
+### INV-DM1: searchable רק כשחוזה-השלמות מתקיים
+**כלל:** רשומת `case_law` נחשבת **searchable** אך ורק כשחוזה-השלמות של [§2א](#2א-case_law--החוזה-הקונקרטי)
+מתקיים במלואו (מזהה קנוני · `case_name`/`court`/`practice_area`/`source_kind` · ≥1 chunk עם
+embedding · metadata לא-ריק). רשומה שנכשלת **מסומנת `searchable=false` ומדווחת ל-health-check —
+לא מוחזרת מ-search ולא מתקבלת בשקט**.
+**מקורות:** ISO 8000 (completeness) · DAMA-UK *Six Primary Dimensions for Data Quality* (2013,
+completeness) · ISO 15489-1:2016 (records reliability/usability) | סטטוס: verified
+**אכיפה:** ולידציית-כתיבה בנקודת-הקליטה ([01-ingest.md](01-ingest.md) צעד 8) + בדיקת-בריאות
+תקופתית שמסמנת backlog; הסינון נאכף בשכבת-החיפוש ([03-retrieval.md](03-retrieval.md)). אוכף את
+[G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש).
+**הפרה ידועה:** ערן סופר 8046/24 אונדקס כ-searchable עם `headnote`/`summary`/`subject_tags`
+ריקים — המסלול הפנימי לא תיזמן חילוץ-מטא-דאטה ([01-ingest INV-ING3](01-ingest.md#inv-ing3-תור-חילוץ-מטא-דאטה--הלכות-לכל-סוג),
+`internal_decisions.py:208`) → ממצא ל-[audit](../audit-report.md).
+
+### INV-DM2: מזהה קנוני יחיד לכל ישות
+**כלל:** לכל ישות **מזהה קנוני אחד**, מנורמל בכתיבה. **אסור** ששדה-המזהה יאחסן ציטוט-מלא —
+`case_number` הוא מספר-תיק מנורמל (`8126-03-25`), **לא** מחרוזת-ציטוט (`ערר 8126/24 פלוני נ' הוועדה
+(נבו...)`). הציטוט המלא חי בשדה ייעודי נפרד (`citation_formatted`, `db.py:1070`), לא במזהה.
+**מקורות:** SSOT (Single Source of Truth — normalization) · E.F. Codd, First Normal Form (CACM
+13(6), 1970) · Martin Kleppmann, *Designing Data-Intensive Applications* (O'Reilly, 2017) | סטטוס: verified
+**אכיפה:** unique partial indexes על המזהה הקנוני (`db.py:904-909`) + נרמול-בכתיבה
+([X1-identifiers.md](X1-identifiers.md)); ציטוט-מלא ב-`citation_formatted` בלבד. אוכף את
+[G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה).
+**הפרה ידועה:** החלטות "סופר" נקלטו עם **ציטוט-מלא כ-`case_number`** (שדה-המזהה של רשומה מכיל את
+מחרוזת-הציטוט במקום מספר-תיק מנורמל) — חיפוש מול `8126-03-25` נכשל, ו-`_normalize_case_number`
+(`db.py:1196-1211`) רק **מטליא בקריאה** (סלחני, לא קנוני), בניגוד ל-[G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה)
+→ ממצא ל-[audit](../audit-report.md).
+
+### INV-DM3: שינוי-תוכן ⇒ re-index
+**כלל:** כל שינוי בתוכן-המקור של ישות מואנדקסת (`content` של chunk, `rule_statement`/`supporting_quote`
+של הלכה, `full_text`/`extracted_text` של מסמך) מפעיל **re-index** של ה-embedding **ושל
+ה-tsvector** הנגזרים. אין embedding או `content_tsv`/`rule_tsv`/`meta_tsv` מיושנים מול התוכן.
+**מקורות:** Pinecone (index freshness / data sync) · Weaviate (re-vectorization on update) ·
+RAG freshness (Lewis et al., 2020, NeurIPS) | סטטוס: verified
+**אכיפה:** טריגר re-embed בנקודת-העדכון + בדיקת-בריאות לגילוי drift; ה-tsvectors `GENERATED ALWAYS
+… STORED` (`db.py:776-788,1083-1090`) מתעדכנים אוטומטית, אך ה-`embedding` **אינו** generated —
+הוא תלוי-טריגר. מפורט ב-[03-retrieval.md](03-retrieval.md). אוכף את
+[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן).
+**הפרה ידועה:** —
+
+---
+
+## 4. מצב קיים מול יעד — audit-findings
+
+ההבדלים בין ה-schema בפועל ל-TARGET. **אלו תסמינים, לא התנהגויות תקינות.** כל פריט אומת מול `db.py`.
+
+- **`case_law` כפולת-תפקיד ללא מזהה מודע-סוג בכתיבה.** טבלה אחת משרתת פסיקה חיצונית **וגם**
+ החלטות-ועדה, מובדלות ב-`source_kind` (`db.py:599`). המזהה הקנוני האמיתי הוא טריפלט
+ (`case_number, source_kind, proceeding_type`, `db.py:904-909`), אך השדה `case_number TEXT
+ UNIQUE NOT NULL` המקורי (`db.py:368`) הוסר רק ב-V15 (`db.py:902-903`) — מורשת שאפשרה את
+ הפרת [INV-DM2](#inv-dm2-מזהה-קנוני-יחיד-לכל-ישות). **יעד:** נרמול-בכתיבה אכוף + ציטוט-מלא רק ב-`citation_formatted`.
+- **`summary` קיים על `case_law` אך לא בחוזה-הקליטה הפנימי.** העמודה קיימת (`db.py:373`) אך
+ המסלול הפנימי אינו ממלא אותה (כפועל-יוצא מהיעדר חילוץ-מטא-דאטה, [INV-ING3](01-ingest.md#inv-ing3-תור-חילוץ-מטא-דאטה--הלכות-לכל-סוג)).
+ **יעד:** searchable מותנה ב-metadata לא-ריק ([INV-DM1](#inv-dm1-searchable-רק-כשחוזה-השלמות-מתקיים)).
+- **שני שדות-סטטוס-חילוץ נפרדים, ללא דגל-`searchable` מפורש.** `extraction_status` +
+ `halacha_extraction_status` (`db.py:603-605`) מתארים תהליך, אך אין שדה יחיד שמסמן "עבר
+ חוזה-שלמות → searchable". **יעד:** דגל/view נגזר ש-search מסנן לפיו, מגובה health-check.
+- **`embedding` אינו `GENERATED` (בניגוד ל-tsvector).** ה-tsvectors מסונכרנים אוטומטית
+ (`db.py:776,780,1083`), אך ה-`embedding vector(1024)` תלוי-טריגר חיצוני — נקודת-drift אפשרית
+ ל-[INV-DM3](#inv-dm3-שינוי-תוכן--re-index). **יעד:** טריגר re-embed מובטח + health-check ל-drift.
+- **`halachot.review_status` כשער-searchable ללא נראות-backlog.** הסינון תקין (`pending_review`
+ מוסתר, `db.py:659`), אך אין נראות כמה ממתינות — תואם את ההפרה הידועה ב-[G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)
+ (10/19 מאושרות, התגלה במקרה). **יעד:** health-check חושף backlog-הלכות.
+
+---
+
+## 5. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md) — invariants גלובליים (G1, G4, G6) + כללי-הנדסה.
+- [01-ingest.md](01-ingest.md) — חוזה-הקליטה שמייצר את הרשומות; חוזה-השלמות כאן אוכף את תוצריו.
+- [03-retrieval.md](03-retrieval.md) — שכבת-האחזור שאוכפת את הסינון searchable + re-index.
+- [X1-identifiers.md](X1-identifiers.md) — נרמול המזהה הקנוני בכתיבה (בסיס ל-INV-DM2).
+- [X5-audit-provenance.md](X5-audit-provenance.md) — שלמות-רשומה + עקיבוּת-מקור.
diff --git a/docs/spec/03-retrieval.md b/docs/spec/03-retrieval.md
new file mode 100644
index 0000000..b054d67
--- /dev/null
+++ b/docs/spec/03-retrieval.md
@@ -0,0 +1,178 @@
+# 03 — אחזור (Retrieval: Corpora · Hybrid/RRF · Attribution · Eval)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומגדיר את **שכבת-האחזור הקנונית (TARGET)** —
+שלושת הקורפוסים, כלי-החיפוש המכוונים לכל אחד, מנגנון ה-hybrid (dense + lexical) ומיזוג ה-RRF,
+עקיבוּת-המקור והרמוניית-המדידה. הוא אוכף את
+[G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש) (חוזה-שלמות לפני "ניתן-לחיפוש"),
+[G5](00-constitution.md#inv-g5-metadata-מלא--הפרדת-קורפוס-נאכפת-בכל-query) (הפרדת-קורפוס בכל query),
+[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן) (re-index),
+[G7](00-constitution.md#inv-g7-מיזוג-rrf--לא-סכום-ציונים) (מיזוג RRF),
+[G8](00-constitution.md#inv-g8-איכות-אחזור-נמדדת--precision--recall) (eval) ו-
+[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) (עקיבוּת-מקור).
+
+> **TARGET, לא תיאור-מצב.** כל מקום שבו הקוד בפועל סוטה מהיעד מתועד כ-**audit-finding** (§5),
+> תסמין לתיקון — לא התנהגות תקינה. כל טענה על הקוד מצוטטת `file:line`.
+
+כשל-השורש שהקובץ מייבש: **3 קורפוסים שחולקים תשתית-אחזור אחת, אך הפרדת-הקורפוס נאכפת רק על
+חלק ממסלולי-ה-query** — כך שפריט מקורפוס אחד דולף לתוצאה של חיפוש בקורפוס אחר (cross-corpus leak).
+
+---
+
+## 1. שלושת הקורפוסים וכלי-החיפוש
+
+| קורפוס | טבלת-אחסון | `source_kind` | כלי-MCP מכוון | אימות `file:line` |
+|--------|------------|----------------|----------------|--------------------|
+| מסמכי-תיק + קורפוס-סגנון דפנה | `document_chunks` | — (מובחן ב-`case_id`/`practice_area`) | `search_decisions` · `search_case_documents` · `find_similar_cases` | `search.py:15,91,145` → `hybrid_search.py:41` (`search_documents_hybrid`) → `db.search_similar` (`hybrid_search.py:56`) |
+| פסיקה חיצונית סמכותית | `case_law` + `precedent_chunks`/`halachot` | `external_upload` | `search_precedent_library` | `search.py`→`precedent_library.py:235` → `search_library` → `hybrid_search.py:89,101` (`source_kind="external_upload"`) |
+| החלטות ועדות-ערר (פנימי) | `case_law` + `precedent_chunks`/`halachot` | `internal_committee` | `search_internal_decisions` | `search.py:228` → `internal_decisions.py:395,411-418` (`source_kind="internal_committee"`) → `hybrid_search.py:89` |
+
+**הבחנת-שם קריטית (לא קורפוס רביעי):** `precedent_search_library` (`server.py:160`) הוא כלי **שונה** —
+מחפש בציטוטים שהיו"ר צירפה ידנית לתיקים (`case_precedents`), לא בקורפוס הפסיקה הסמכותית.
+`search_precedent_library` (`server.py:280`) הוא הכלי לקורפוס החיצוני. אל תבלבל ביניהם.
+
+הקורפוס החיצוני והפנימי **חולקים טבלה אחת** (`case_law`), מובחנים ב-`source_kind` בלבד
+([02-data-model §2א](02-data-model.md#2א-case_law--החוזה-הקונקרטי)). שניהם רצים דרך **אותן** פונקציות-DB
+(`search_precedent_library_semantic`/`_lexical`) — לכן הפרדת-הקורפוס היא **תנאי-סינון בתוך אותה שאילתה**,
+ושם נולדת ההפרה ב-§5.
+
+---
+
+## 2. עיצוב ה-hybrid retrieval
+
+לכל קורפוס שני retrievers הטרוגניים המאוחים ב-RRF, ולא בסכום-ציונים — ראה [INV-RET3](#inv-ret3-מיזוג-retrievers-הטרוגניים-ב-rrf-בלבד):
+
+1. **Dense (semantic)** — דמיון-קוסינוס מול `embedding vector(1024)` (voyage). פסיקה:
+ `search_precedent_library_semantic` (`db.py:3143`); מסמכי-תיק: `db.search_similar`.
+2. **Lexical (BM25-style)** — `ts_rank_cd` מול `content_tsv`/`rule_tsv`/`meta_tsv` (Postgres FTS).
+ פסיקה: `search_precedent_library_lexical` (`db.py:3366`). מופעל כש-`BM25_HYBRID_ENABLED`
+ (`hybrid_search.py:139`).
+3. **מיזוג sem+lex** — `_merge_sem_lex` (`hybrid_search.py:240-308`), נוסחת
+ `rrf_score = 1/(k+sem_rank) + 1/(k+lex_rank)` (`hybrid_search.py:256`).
+4. **שכבת-multimodal (אופציונלית)** — כש-`MULTIMODAL_ENABLED`, עמודי-תמונה (voyage-multimodal-3)
+ מאוחים לטקסט ב-RRF נפרד: `_merge` (`hybrid_search.py:311-389`), `text_weight/(k+rank) +
+ img_weight/(k+rank)` (`hybrid_search.py:356-357`).
+5. **Diversity cap (MMR-style)** — `_diversify_by_case_law` (`hybrid_search.py:196-225`): לכל היותר
+ `max_per_case_law` hits לכל `case_law_id`, כדי שפסק-דין יחיד לא ישתלט על הרשימה.
+
+> **למה RRF ולא סכום משוקלל:** קוסינוס (~0.4–0.7) ו-`ts_rank_cd` (~0.001–0.5, תלוי-אורך-שאילתה)
+> חיים בסקיילים שונים — סכום משוקלל היה נותן לצד אחד להשתלט במקרה. RRF מאחד **לפי דירוג**, ולכן
+> עמיד להבדלי-סקייל (`hybrid_search.py:248-252,319-323`). תואם feedback קיים (RRF, לא weighted-sum).
+
+---
+
+## 3. Invariants של התחום
+
+### INV-RET1: הפרדת-קורפוס נאכפת ב-100% ממסלולי-ה-query
+**כלל:** הפרדת 3 הקורפוסים נאכפת בכל מסלול-אחזור — **גם בסינון ה-chunks וגם בסינון ההלכות**.
+אין פריט מקורפוס אחד שמופיע בתוצאת חיפוש שכוון לקורפוס אחר. כל ענף-SQL (semantic/lexical,
+chunks/halachot) נושא את אותו תנאי-`source_kind`.
+**מקורות:** Pinecone — *Implement multitenancy* (metadata-filter isolation per tenant) · RAG
+attribution (Lewis et al., 2020, NeurIPS — pinned non-leaking provenance) · ISO 8000 (Data
+quality / conformance) | סטטוס: verified
+**אכיפה:** תנאי-`source_kind` בכל ענף-SQL בשכבת-החיפוש; בדיקת-בריאות שמריצה שאילתת-ביקורת
+(חיפוש מכוון-קורפוס שמחזיר פריט בעל `source_kind` זר = כשל). אוכף את
+[G5](00-constitution.md#inv-g5-metadata-מלא--הפרדת-קורפוס-נאכפת-בכל-query).
+**הפרה ידועה:** משימה #56 — `halacha_filters` **אינם** כוללים `cl.source_kind` ב-
+`search_precedent_library_semantic` (`db.py:3168`, ענף ה-halacha; לעומת `chunk_filters` שכן —
+`db.py:3169`) **וב**-`search_precedent_library_lexical` (`db.py:3401` מול `db.py:3402`). שני
+ה-`halacha_sql` עושים `JOIN case_law cl` בלי לסנן `source_kind` (`db.py:3236-3238`, `db.py:3475-3477`)
+→ הלכות מהקורפוס הפנימי דולפות לתוצאות החיפוש בקורפוס החיצוני ולהפך → ממצא ל-[audit](../audit-report.md).
+
+### INV-RET2: אין החזרה/אינדוקס בלי metadata מלא + locator פתיר
+**כלל:** פריט אינו מוחזר מ-search (ואינו נחשף לאחזור) אלא אם **שדות-החובה שלו מולאו**
+([G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)) **ובידו locator פתיר למקור**
+(`case_law_id`/`document_id` + מזהה-עמוד/chunk). רשומה ללא metadata לא-ריק או ללא chunk עם
+embedding מסומנת `searchable=false` ולא מוחזרת ([02-data-model INV-DM1](02-data-model.md#inv-dm1-searchable-רק-כשחוזה-השלמות-מתקיים)).
+**מקורות:** Pinecone (metadata filtering — completeness לפני שליפה) · RAG attribution (Lewis et
+al., 2020) · ISO 8000 (completeness) | סטטוס: verified
+**אכיפה:** חוזה-שלמות בנקודת-הקליטה ([02-data-model §2](02-data-model.md#2-חוזה-שלמות-לכל-ישות-completeness-contract))
++ סינון בשכבת-החיפוש (`embedding IS NOT NULL`, `db.py:3239,3271`; `length(trim(content))>=50`,
+`db.py:3274`) + בדיקת-בריאות שחושפת backlog. אוכף את
+[G5](00-constitution.md#inv-g5-metadata-מלא--הפרדת-קורפוס-נאכפת-בכל-query).
+**הפרה ידועה:** ערן סופר 8046/24 — נקלטה בלי metadata (headnote/summary/tags ריקים), היעדר
+תיזמון חילוץ-מטא-דאטה במסלול הפנימי ([01-ingest INV-ING3](01-ingest.md#inv-ing3-תור-חילוץ-מטא-דאטה--הלכות-לכל-סוג)),
+אך ללא דגל-`searchable` מפורש שימנע את חשיפתה לאחזור → ממצא ל-[audit](../audit-report.md).
+
+### INV-RET3: מיזוג retrievers הטרוגניים ב-RRF בלבד
+**כלל:** מיזוג תוצאות בין retrievers שונים (semantic↔lexical, text↔image) נעשה **אך ורק
+לפי דירוג (Reciprocal Rank Fusion)** — לעולם לא סכום/ממוצע ציונים גולמיים, שכן ציונים בסקיילים
+שונים אינם בני-השוואה ישירה.
+**מקורות:** Elastic — *Reciprocal Rank Fusion* · Weaviate — *Hybrid Search Explained* · Manning,
+Raghavan & Schütze, *Introduction to Information Retrieval* (CUP, 2008) | סטטוס: verified
+**אכיפה:** מיזוג sem+lex ב-`_merge_sem_lex` (`hybrid_search.py:240-308`, נוסחה ב-`:256`) ומיזוג
+text+image ב-`_merge` (`hybrid_search.py:311-389`, נוסחה ב-`:356-357`), שניהם עם
+`k = MULTIMODAL_RRF_K`. אוכף את [G7](00-constitution.md#inv-g7-מיזוג-rrf--לא-סכום-ציונים).
+**מצב:** **כבר ממומש** (codify, לא gap) — הקוד הקיים מיישם RRF נכון בשני המיזוגים. ה-invariant
+מקבע את ההתנהגות הקיימת כחוזה. **הפרה ידועה:** —
+
+### INV-RET4: איכות-אחזור נמדדת ב-eval harness עומד (precision + recall)
+**כלל:** איכות-האחזור **נמדדת אמפירית** — precision **ו**-recall — מול **סט-שאילתות מתויג קבוע**
+(labeled query set) ב-eval harness עומד. כל שינוי בשכבת-האחזור (משקלי-RRF, `k`, סף-chunk, embedder)
+מלווה במדידה לפני/אחרי; אין כוונון "לפי תחושה".
+**מקורות:** Manning, Raghavan & Schütze, *Introduction to Information Retrieval* (CUP, 2008 — fixed
+relevance judgments, precision/recall) · RAG evaluation literature (Lewis et al., 2020 ואחריו) ·
+Elastic — *relevance evaluation guidance* | סטטוס: verified
+**אכיפה:** eval harness עם gold-set מתויג + בדיקת-בריאות תקופתית; שער-CI על שינוי שכבת-האחזור.
+אוכף את [G8](00-constitution.md#inv-g8-איכות-אחזור-נמדדת--precision--recall).
+**הפרה ידועה (GAP):** אין כיום eval harness ולא gold-set — קיים רק `telemetry.log_search_bg`
+(`search.py:62,118,190,271`; `precedent_library.py:280`) שמתעד שאילתות בפועל, אך **אינו מודד
+precision/recall מול תיוג** (תצפית, לא הערכה). היעד: harness שמריץ סט קבוע ומחזיר metrics →
+ממצא ל-[audit](../audit-report.md).
+
+### INV-RET5: כל span מוחזר עקיב למקורו
+**כלל:** כל קטע מוחזר נושא **עקיבוּת-מקור מלאה** — מזהה-מסמך/פסק-דין (`case_law_id`/`document_id`/
+`case_number`) **ו**-locator בתוכו (`page_number` / `chunk_id` / `supporting_quote` להלכה). פלט
+ללא ייחוס פתיר אינו תקין; היו"ר חייבת לאמת כל ציטוט מול מקורו.
+**מקורות:** Council of Europe / CEPEJ — *European Ethical Charter on AI in judicial systems*
+(2018, traceability) · RAG attribution (Lewis et al., 2020) · ISO 15489-1:2016 (records
+authenticity/integrity) | סטטוס: verified
+**אכיפה:** כל פורמטר-תוצאה כולל מזהה + locator: `search.py:77-86` (case_number/page/section),
+`_format_internal_row` (`search.py:322-343`: case_number/case_name/court + content/page או
+rule/quote להלכה). עקיבוּת מלאה מפורטת ב-[X5-audit-provenance.md](X5-audit-provenance.md). אוכף את
+[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai).
+**הפרה ידועה:** —
+
+---
+
+## 4. re-index ושינוי-תוכן (G6)
+
+האחזור מסתמך על embeddings מסונכרנים מול התוכן. ה-tsvectors (`content_tsv`/`rule_tsv`/`meta_tsv`)
+הם `GENERATED ALWAYS … STORED` (`db.py:778,782,1086`) ולכן מתעדכנים אוטומטית; אך ה-`embedding
+vector(1024)` **אינו** generated — הוא תלוי-טריגר-חיצוני, נקודת-drift אפשרית
+([02-data-model INV-DM3](02-data-model.md#inv-dm3-שינוי-תוכן--re-index)). שינוי-תוכן חייב להפעיל
+re-embed; בדיקת-בריאות מגלה embeddings מיושנים. אוכף את
+[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן).
+
+---
+
+## 5. מצב קיים מול יעד — audit-findings
+
+ההבדלים בין הקוד בפועל ל-TARGET. **אלו תסמינים, לא התנהגויות תקינות.** כל פריט אומת מול הקוד.
+
+- **דליפת-הלכות חוצת-קורפוס (משימה #56).** `halacha_filters` נפתחים רק עם `review_status`
+ (`db.py:3168`, `db.py:3401`) ואינם מוסיפים `cl.source_kind`, בעוד `chunk_filters` כן
+ (`db.py:3169`, `db.py:3402`). שני ה-`halacha_sql` עושים `JOIN case_law` בלי סינון
+ (`db.py:3236-3242`, `db.py:3463-3482`). **תסמין:** חיפוש בקורפוס החיצוני
+ (`search_precedent_library`, `source_kind="external_upload"`) יכול להחזיר הלכה שמקורה
+ בהחלטת-ועדה פנימית — ולהפך עבור `search_internal_decisions` (`source_kind="internal_committee"`,
+ `internal_decisions.py:418`). **יעד:** `halacha_filters` יתחילו ב-`cl.source_kind = '{source_kind}'`
+ בדיוק כמו `chunk_filters` ([INV-RET1](#inv-ret1-הפרדת-קורפוס-נאכפת-ב-100-ממסלולי-ה-query)).
+- **אין eval harness — מדידת-איכות לא קיימת.** רק `telemetry.log_search_bg` מתעד שאילתות
+ (`search.py:62,118,190,271`); אין gold-set מתויג ואין precision/recall. **יעד:** harness עומד
+ ([INV-RET4](#inv-ret4-איכות-אחזור-נמדדת-ב-eval-harness-עומד-precision--recall)).
+- **`search_decisions` מתעד אזהרה כשאין `practice_area` אך לא חוסם.** ללא פילטר-תחום החיפוש
+ עלול לערבב תחומים משפטיים (`search.py:45-49,172-176` — `logger.warning`, ממשיך). **יעד:** הפרדה
+ לפי תחום נאכפת, לא מומלצת בלבד — תואם את עקרון ההפרדה ב-[G5](00-constitution.md#inv-g5-metadata-מלא--הפרדת-קורפוס-נאכפת-בכל-query).
+- **`embedding` אינו `GENERATED` (בניגוד ל-tsvector).** נקודת-drift אפשרית בין תוכן ל-embedding
+ אחרי עדכון ([§4](#4-re-index-ושינוי-תוכן-g6); תואם [02-data-model](02-data-model.md#inv-dm3-שינוי-תוכן--re-index)).
+ **יעד:** טריגר re-embed מובטח + health-check.
+
+---
+
+## 6. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md) — invariants גלובליים (G4–G9) + כללי-הנדסה.
+- [01-ingest.md](01-ingest.md) — חוזה-הקליטה שמייצר את ה-chunks/embeddings שהאחזור שולף.
+- [02-data-model.md](02-data-model.md) — חוזה-השלמות (searchable) + re-index שהאחזור מסנן לפיהם.
+- [05-qa-review.md](05-qa-review.md) — שער-הלכה הידני (`review_status`) שמגדיר אילו הלכות searchable.
+- [X5-audit-provenance.md](X5-audit-provenance.md) — עקיבוּת-מקור מלאה של כל span מוחזר (בסיס ל-INV-RET5).
diff --git a/docs/spec/04-analysis-writing.md b/docs/spec/04-analysis-writing.md
new file mode 100644
index 0000000..0983933
--- /dev/null
+++ b/docs/spec/04-analysis-writing.md
@@ -0,0 +1,186 @@
+# 04 — ניתוח וכתיבה (Analysis & Writing)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומפרט את שלב **הסיוע-בכתיבה** —
+חילוץ הטענות, ארכיטקטורת 12 הבלוקים, וסגנון דפנה. הוא אוכף את
+[INV-G11](00-constitution.md#inv-g11-תוכן-החלטה-מנומקת) (תוכן החלטה מנומקת).
+
+> **⚠ מודל-סמכות שונה מ-01–03.** זהו קובץ **תוכן-משפטי**, לא קובץ-הנדסה. לפי החוקה
+> (§2 עיקרון 2, §5ב) הסמכות עליו היא **היו"ר (עו"ד דפנה תמיר) + מסמכי-הפרויקט** —
+> [block-schema.md](../block-schema.md), [decision-methodology.md](../decision-methodology.md),
+> [legal-decision-lessons.md](../legal-decision-lessons.md),
+> [corpus-analysis.md](../corpus-analysis.md), [skills/decision/SKILL.md](../../skills/decision/SKILL.md).
+> ה-invariants כאן **אינם** כפופים לפרוטוקול ≥3-המקורות החיצוני, ו**אינם** נושאים
+> `סטטוס: verified / ⚠ UNVERIFIED`. במקום `מקורות: … | סטטוס` הם נושאים `מקור-סמכות:`.
+> מסמכי-הפרויקט הם המקור המוסמך; קובץ זה מצטט אותם בגובה-ספ, לא משכפל את ההגדרות.
+
+---
+
+## 1. חילוץ טענות → טיעונים מאוגדים
+
+לפני הכתיבה, חומרי-המקור הופכים למבנה-נתונים שמזין את הבלוקים. שני שלבים:
+
+### 1.1 חילוץ טענות גולמיות (claims)
+
+`extract_claims(case_number, doc_title="", party_hint="")` קורא לכתבי-הטענות בתיק,
+ושומר טענות גולמיות ב-DB. הוא מסנן למסמכים מסוג `appeal` / `response` / `objection`
+(אלא אם צוין `doc_title` מפורש), ולכל מסמך קורא ל-`claims_extractor.extract_and_store_claims`
+— ראה `mcp-server/src/legal_mcp/tools/documents.py:300-347`.
+
+כל טענה נשמרת עם `party_role` מתוך התפקידים המוכרים: **`appellant` (עוררים)** ·
+**`respondent` (משיבים)** · **`committee` (ועדה מקומית)** · **`permit_applicant`
+(מבקשי היתר)** · **`appraiser` (שמאי)**. `get_claims(case_number, party_role="")`
+שולף ומציג אותן בעברית, עם סינון אופציונלי לפי תפקיד
+(`documents.py:350-385`; מיפוי-העברית ב-`:370-376`).
+
+### 1.2 כינוס לטיעונים משפטיים מובחנים (legal arguments)
+
+`aggregate_claims_to_arguments(case_number, force=False)` מכנס את הפרופוזיציות
+הגולמיות לטיעונים משפטיים מובחנים (de-duplication) דרך
+`argument_aggregator.aggregate_claims_to_arguments`; `force=True` מוחק טיעונים קיימים
+ומחשב מחדש — ראה `mcp-server/src/legal_mcp/tools/legal_arguments.py:11-33`.
+`get_legal_arguments(case_number, party="")` שולף את הטיעונים המאוגדים, מקובצים לפי
+צד (`appellant`/`respondent`/`committee`/`permit_applicant`/`unknown`); אם אין —
+הוא מחזיר הנחיה להריץ קודם את הכינוס (`legal_arguments.py:36-83`).
+
+> **מדוע זה חשוב לתוכן:** הטיעונים המאוגדים הם הקלט ל-[INV-WR3](#inv-wr3-מענה-לכל-טענה-של-הצד-המפסיד)
+> (מענה לכל טענה עיקרית) ול-[INV-WR4](#inv-wr4-בלוק-ז--טענות-מקוריות-בלבד) (הפרדת טענות
+> מקוריות מהשלמות). הסינון לפי `party_role` מאפשר לזהות את הצד המפסיד ולוודא שכל טיעון
+> שלו מקבל מענה בבלוק י.
+
+---
+
+## 2. ארכיטקטורת 12 הבלוקים (סיכום)
+
+המבנה הפורמלי המלא — content model, constraints, משקלות, ופרמטרי-עיבוד לכל בלוק —
+מוגדר ב-[block-schema.md](../block-schema.md) (המקור המוסמך). כאן רק מפת-גובה:
+
+| בלוק | תפקיד | CREAC | תוכן מהותי? |
+|------|--------|-------|-------------|
+| א–ד | כותרת מוסדית · הרכב · צדדים · "החלטה" | — | לא (template-fill) |
+| ה | פתיחה ("לפנינו…") | C ראשוני | קל |
+| **ו** | רקע עובדתי ("פתח דבר") | — | **כן — עובדות בלבד** |
+| **ז** | טענות הצדדים | — | **כן — טענות מקוריות בלבד** |
+| ח | הליכים בפני הוועדה | — | כן (תיעוד, ללא הערכה) |
+| ט | תכניות חלות (אופציונלי) | R | כן (כשיש מורכבות תכנונית) |
+| **י** | דיון והכרעה | full-CREAC | **כן — ה-ratio decidendi** |
+| יא | סיכום / סוף דבר | C אחרון | קל |
+| יב | חתימות | — | לא |
+
+יסודות תיאורטיים (CREAC · FJC Judicial Writing Manual · DITA · Akoma Ntoso),
+תלויות-בין-בלוקים, וכללי-ולידציה — ב-[block-schema.md](../block-schema.md) §§1, 5, 6.
+מתודולוגיית-המשקלות (Communicative / Reader-attention / Judicial-review / Empirical)
+— שם §4. **טיוטת-ביניים** (Pre-Ruling Draft) בוחרת תת-קבוצת בלוקים (ו, ט, ז, ח) —
+block-schema.md §7; שלב-החילוץ השמאי שלה (`extract_appraiser_facts`) מזין את בלוק ט.
+
+> **התמקדות לפי feedback היו"ר:** הסיוע מתמקד בבלוקים המהותיים (ו–יב); בלוקים א–ד
+> ממולאים מ-template ואינם דורשים ניתוח. ראה `MEMORY.md` → "התעלם מכותרות".
+
+---
+
+## 3. סגנון דפנה (סיכום)
+
+מדריך-הסגנון המלא הוא [skills/decision/SKILL.md](../../skills/decision/SKILL.md);
+המתודולוגיה האנליטית ("איך לחשוב לפני איך לכתוב") היא
+[decision-methodology.md](../decision-methodology.md). נקודות-מפתח:
+
+- **טון לפי סוג-ערר** — רישוי (1xxx) חם יחסית; היטל-השבחה (8xxx) ופיצויים ס'197 (9xxx)
+ קרים ויבשים (SKILL.md §1; methodology §א.2).
+- **מבנה הדיון (בלוק י)** — נפתח במסקנה (CREAC: C→R→E→A→C), סילוגיזם לכל סוגיה,
+ steel-manning של הצד המפסיד, ציטוט-פסיקה ב"סנדוויץ'" (methodology §§ד, ו, ז).
+- **מסלול-דיון לפי תוצאה** — דחייה (עיגולים קונצנטריים) · קבלה (נימוק-נימוק) · קבלה
+ חלקית (מיפוי-מתחים) · היטל-השבחה (פתיחה ישירה) — SKILL.md §7.3; block-schema.md בלוק י.
+- **3 מקורות-פסיקה נפרדים** — אסור לבלבל ביניהם (SKILL.md §7.5; ראה גם
+ [03-retrieval.md](03-retrieval.md) לשכבת-האחזור שמזינה אותם).
+- **לקחים מצטברים** — [legal-decision-lessons.md](../legal-decision-lessons.md) +
+ ביטויי-מעבר; מתעדכנים מפידבק-היו"ר ומ-Hermes (ראה forward-ref [07-learning.md](07-learning.md)).
+
+---
+
+## 4. Invariants של התחום — תוכן החלטה מנומקת
+
+חמשת ה-invariants הבאים הם **פאֶטים של [INV-G11](00-constitution.md#inv-g11-תוכן-החלטה-מנומקת)**.
+כולם נושאים `מקור-סמכות` (היו"ר + מסמכי-הפרויקט), **ללא** שדה-מקורות-חיצוני ו**ללא**
+סטטוס-אימות — כמתחייב מהבחנת שתי-הסמכויות בחוקה (§5).
+
+### INV-WR1: רקע ניטרלי (בלוק ו) — עובדות בלבד
+**כלל:** בלוק ו מציג **עובדות בלבד** ואינו טוען. אסורות מילות-ערך/שיפוט ("חריג",
+"בעייתי", "למרבה הפליאה") ואסורים ציטוטים ישירים מצדדים (אלה שייכים לבלוק ז). החלטות
+קודמות מובאות כעובדה יבשה ("ביום X נדחתה תכנית Y"), ללא נימוקים. ניטרליות אינה הסתרה:
+עובדה מהותית התומכת בצד המפסיד **חייבת** להופיע.
+**מקור-סמכות:** היו"ר (עו"ד דפנה תמיר) + [block-schema.md](../block-schema.md) (בלוק ו,
+§5.2 "רקע ניטרלי") + [decision-methodology.md](../decision-methodology.md) §ח.2.
+**אכיפה:** ולידציית-תוכן בבלוק ו (סעיף עם ציטוט-צד או מילת-שיפוט → לא שייך כאן) + שערי
+QA; מפורט ב-[05-qa-review.md](05-qa-review.md).
+**הפרה ידועה:** —
+
+### INV-WR2: ללא כפילות (בלוק י מפנה, לא חוזר)
+**כלל:** בלוק י (דיון) **מפנה** לעובדות ולטענות שכבר הוצגו בבלוקים הקודמים ("כאמור
+בסעיף X לעיל", "כפי שפורט") — ואינו חוזר עליהן. חריג יחיד: חזרה מכוונת עם שכבת-ניתוח
+חדשה ("נשוב על כך כי…"). אין עובדות חדשות בדיון שלא הופיעו ברקע.
+**מקור-סמכות:** היו"ר + [block-schema.md](../block-schema.md) (בלוק י, §5.2 "ללא
+כפילות") + [skills/decision/SKILL.md](../../skills/decision/SKILL.md) §9.1.
+**אכיפה:** ולידציית-מבנה (עובדה בדיון ללא עוגן ברקע = flag) + שערי QA;
+מפורט ב-[05-qa-review.md](05-qa-review.md).
+**הפרה ידועה:** —
+
+### INV-WR3: מענה לכל טענה של הצד המפסיד
+**כלל:** כל **טענה עיקרית** שהוצגה בבלוק ז — ובמיוחד של הצד המפסיד — מקבלת **מענה
+מנומק** בבלוק י (ישיר, "למעלה מן הצורך", או מקובץ עם דומותיה). מותר לא להכריע בטענה
+נחוצה-פחות ("נוכח מסקנתנו לעיל, אין צורך…"), אך אסור להתעלם מטענה מרכזית — הצד המפסיד
+חייב לראות שהוועדה שקלה את יסודות עמדתו (steel-manning).
+**מקור-סמכות:** היו"ר + [decision-methodology.md](../decision-methodology.md) §§ג.2, ו.2 +
+[block-schema.md](../block-schema.md) (בלוק י MUST: "מענה לכל טענה" §5.4) +
+[skills/decision/SKILL.md](../../skills/decision/SKILL.md) §6.2.
+**אכיפה:** מיפוי טענות-בלוק-ז → מענה-בלוק-י (נשען על §1.2, הטיעונים המאוגדים) + שערי QA;
+מפורט ב-[05-qa-review.md](05-qa-review.md).
+**הפרה ידועה:** —
+
+### INV-WR4: בלוק ז — טענות מקוריות בלבד
+**כלל:** בלוק ז מכיל **אך ורק** טענות מכתבי-הטענות המקוריים (כתב-ערר, כתב-תשובה).
+תוכן מהשלמות-טיעון, החלטות-ביניים, ותגובות-מאוחרות → **בלוק ח** (הליכים), לא בלוק ז.
+הצגת-הטענות היא בנאמנות וללא הערכה ("טענה זו חלשה") — ההערכה שייכת לבלוק י.
+**מקור-סמכות:** היו"ר + [block-schema.md](../block-schema.md) (בלוק ז Sources +
+§5.2 "טענות מקוריות בלבד") + [skills/decision/SKILL.md](../../skills/decision/SKILL.md) §4.
+**אכיפה:** סיווג-מקור של טענה בעת החילוץ (`extract_claims` מסנן `appeal`/`response`/
+`objection`; מסמכי פוסט-דיון מתויגים `is_post_hearing` ומופנים לבלוק ח — block-schema.md §7)
++ שערי QA; מפורט ב-[05-qa-review.md](05-qa-review.md).
+**הפרה ידועה:** —
+
+### INV-WR5: "מבחן-השופט" — החלטה עצמאית וקריאה
+**כלל:** ההחלטה חייבת להיות **עצמאית וקריאה לשופט שלא מכיר את התיק** — תשתית עובדתית
+מלאה (בלוק ו), תיעוד procedural-fairness (בלוק ח), והנמקה שעומדת בבדיקת סבירות
+ומידתיות (בלוק י). הקורא לא נדרש לחומרי-המקור כדי להבין את ההחלטה ואת הצדקתה.
+**מקור-סמכות:** היו"ר + [block-schema.md](../block-schema.md) §4.3 ("מבחן השופט" /
+Judicial-Review weight) + [decision-methodology.md](../decision-methodology.md) §יב
+(רשימת-ביקורת) + [corpus-analysis.md](../corpus-analysis.md).
+**אכיפה:** שער QA סופי ("מבחן-השופט") על ההחלטה כיחידה שלמה;
+מפורט ב-[05-qa-review.md](05-qa-review.md).
+**הפרה ידועה:** —
+
+---
+
+## 5. צ'קליסט-תוכן לפי סוג-ערר
+
+בלוק י מקבל **צ'קליסט-תוכן** המוזרק אוטומטית ל-prompt לפי סוג-הערר, מתוך
+`CONTENT_CHECKLISTS` ב-`mcp-server/src/legal_mcp/services/lessons.py:355`. הבורר
+(`lessons.py:532-555`) ממפה לסוג: `tama38` (תמ"א 38) · `betterment_levy` (היטל-השבחה) ·
+`licensing_property` · `licensing_threshold` (שאלת-סף) · `licensing_substantive`
+(ברירת-מחדל לרישוי). הצ'קליסט מבטיח שהדיון מכסה את הנושאים התכנוניים/המשפטיים שדפנה
+מכסה בפועל בקורפוס — ראה [corpus-analysis.md](../corpus-analysis.md) §§3, 6 לדפוסי-התוכן
+ולפער שנסגר (§5.3). זהו מנגנון-תוכן באחריות היו"ר, לא חוק-הנדסה.
+
+---
+
+## 6. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md#inv-g11-תוכן-החלטה-מנומקת) — INV-G11 + הבחנת
+ שתי-הסמכויות (תוכן-משפטי מול הנדסה).
+- [03-retrieval.md](03-retrieval.md) — שכבת-האחזור (3 קורפוסי-פסיקה) שמזינה ציטוטים לבלוק י.
+- [05-qa-review.md](05-qa-review.md) — שערי-QA שאוכפים את INV-WR1–WR5 + שערים אנושיים.
+- [06-export.md](06-export.md) — ייצוא DOCX לפי תבנית-דפנה (אחרי הכתיבה).
+- [07-learning.md](07-learning.md) — לולאת פידבק-היו"ר + Hermes שמעדכנת lessons/SKILL.
+- מסמכי-המקור המוסמכים: [block-schema.md](../block-schema.md) ·
+ [decision-methodology.md](../decision-methodology.md) ·
+ [legal-decision-lessons.md](../legal-decision-lessons.md) ·
+ [corpus-analysis.md](../corpus-analysis.md) ·
+ [skills/decision/SKILL.md](../../skills/decision/SKILL.md).
diff --git a/docs/spec/05-qa-review.md b/docs/spec/05-qa-review.md
new file mode 100644
index 0000000..054bf1d
--- /dev/null
+++ b/docs/spec/05-qa-review.md
@@ -0,0 +1,198 @@
+# 05 — בקרת איכות ושערים אנושיים (QA & Human Review)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומפרט את שלב **הביקורת** לפני
+ייצוא: (1) **שערי-QA אוטומטיים** (`validate_decision` — 6 בדיקות) ו-(2) **שערים אנושיים**
+(אישור הלכה, בחירת תוצאה, פידבק היו"ר). הוא אוכף את
+[INV-G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)
+(שערים אנושיים) ואת [INV-G11](00-constitution.md#inv-g11-תוכן-החלטה-מנומקת) (תוכן מנומק).
+
+> **⚠ קובץ מעורב — שני מודלי-סמכות.** לפי החוקה (§3, §5):
+> - **שערי-הממשל** (שערים אנושיים, שער-הייצוא) הם **invariants הנדסיים** במודל
+> הממשל-שיפוטי → נושאים `מקורות:` (NCSC/JTC · CEPEJ 2018 · FJC) + `סטטוס: verified`.
+> - **מכניקת בדיקות-התוכן** (מה הבדיקה האוטומטית בוחנת בפועל — רקע ניטרלי, ללא כפילות,
+> כיסוי-טענות) היא **תוכן-משפטי** → נושאת `מקור-סמכות:` (היו"ר + מסמכי-הפרויקט +
+> [04-analysis-writing.md](04-analysis-writing.md)), **ללא** מקורות חיצוניים וללא סטטוס.
+
+---
+
+## 1. שערי-QA אוטומטיים — `validate_decision`
+
+`validate_decision(case_number)` (wrapper ב-`tools/drafting.py:363`, נחשף ב-`server.py:551`)
+טוען את בלוקי-ההחלטה והטענות מה-DB ומריץ **6 בדיקות**, אז כותב את התוצאות לטבלת
+`qa_results` ומחזיר `passed` / `critical_failures` / `export_blocked`. הליבה:
+`services/qa_validator.py:292` (`validate_decision`). כל בדיקה מחזירה
+`{name, passed, errors, severity}`; `severity ∈ {critical, warning}`.
+
+> **חישוב החסימה:** `critical_failures = Σ(not passed ∧ severity=="critical")`
+> (`qa_validator.py:338`), ו-`export_blocked = critical_failures > 0`
+> (`qa_validator.py:362`). בדיקת `warning` שנכשלת מורידה `passed=False` אך **אינה** חוסמת
+> ייצוא. ראה [§3 / INV-QA3](#inv-qa3-החלטה-לא-מיוצאת-עם-כשל-קריטי-governance--g10).
+
+### 1.1 ששת השערים
+
+| # | בדיקה | מה בוחנת | severity | פונקציה (file:line) |
+|---|-------|----------|----------|---------------------|
+| 1 | `neutral_background` | רקע (בלוק ו) ללא מילות-שיפוט (`VALUE_WORDS`) וללא ציטוט-צד (`QUOTE_INDICATORS`) | **warning** | `check_neutral_background` — `qa_validator.py:66` |
+| 2 | `claims_coverage` | כל טענה מבלוק ז נענתה בבלוק י (בדיקה סמנטית דרך Claude) | **critical** | `check_claims_coverage` — `qa_validator.py:107` |
+| 3 | `weight_compliance` | משקל-מילים של כל בלוק בטווח לפי סוג-ערר (`WEIGHT_RANGES`) | **warning** | `check_weight_compliance` — `qa_validator.py:177` |
+| 4 | `structural_integrity` | בלוקי-חובה קיימים (ה, ז, י, יא) + בלוק י הוא הכבד ביותר | **critical** | `check_structural_integrity` — `qa_validator.py:206` |
+| 5 | `no_duplication` | אין משפט מבלוק ו (>30 תווים) שחוזר מילה-במילה בבלוק י | **warning** | `check_no_duplication` — `qa_validator.py:235` |
+| 6 | `sequential_numbering` | מספור-סעיפים רציף בכל הבלוקים, מתחיל ב-1, ללא פערים | **warning** | `check_sequential_numbering` — `qa_validator.py:261` |
+
+### 1.2 דקויות חשובות (אל תניח — מהקוד)
+
+- **רק 2 שערים קריטיים** חוסמים ייצוא: `claims_coverage` ו-`structural_integrity`. שאר
+ הארבעה הם `warning` בנתיב הרגיל — `qa_validator.py:86, 202, 257, 286`.
+- **דקות `neutral_background` — שני נתיבי-החזרה:** הנתיב הרגיל מסומן `warning` (`:86`); נתיב
+ ה-fallback של בלוק-ו ריק/חסר מסומן `critical` (`:70`) **אך מחזיר `passed=True`**, ולכן
+ אינו נספר ב-`critical_failures` ואינו חוסם ייצוא. תפקודית — השער אינו חוסם.
+- **`claims_coverage` סובלני ל-20%:** עובר אם `len(missing) ≤ total*0.2`
+ (`qa_validator.py:170`). מסנן לטענות `appellant`/`respondent` שאינן מבלוק-ז
+ (`qa_validator.py:120-129`), כי טענות `committee`/`permit_applicant` הן עמדות-הגנה ולא
+ דורשות מענה. כשל-פענוח של Claude → fallback `passed=True` כדי לא לחסום ייצוא על תקלת-LLM
+ (`qa_validator.py:148-152`).
+- **`neutral_background` ריק = עובר:** בלוק ו ריק/חסר מחזיר `passed=True`
+ (`qa_validator.py:69`). הבדיקה היא lexical (רשימת-מילים + regex), לא סמנטית.
+- **`no_duplication` תופס רק חזרה מילה-במילה** (substring) — לא פרפרזה.
+- כל ריצה **מנקה** את `qa_results` הקודמות של התיק ואז כותבת מחדש (`qa_validator.py:344-357`).
+
+### 1.3 שערי-התוכן מתפעלים את WR1–WR3
+
+שלוש מ-6 הבדיקות הן ההפעלה האוטומטית (חלקית) של ה-invariants של התוכן ב-
+[04-analysis-writing.md](04-analysis-writing.md):
+
+| שער QA | invariant-תוכן | פער (אוטומטי מול הגדרה) |
+|--------|----------------|--------------------------|
+| `neutral_background` | [INV-WR1](04-analysis-writing.md#inv-wr1-רקע-ניטרלי-בלוק-ו--עובדות-בלבד) | lexical בלבד — לא תופס שיפוט עקיף; warning, לא critical |
+| `no_duplication` | [INV-WR2](04-analysis-writing.md#inv-wr2-ללא-כפילות-בלוק-י-מפנה-לא-חוזר) | מילה-במילה בלבד — לא תופס כפילות מנוסחת-מחדש |
+| `claims_coverage` | [INV-WR3](04-analysis-writing.md#inv-wr3-מענה-לכל-טענה-של-הצד-המפסיד) | סמנטי (Claude), סובלני ל-20% חוסר |
+
+ראה [INV-QA4](#inv-qa4-שערי-התוכן-האוטומטיים-אוכפים-את-wr1wr3-content--g11). WR4 (טענות
+מקוריות) ו-WR5 ("מבחן-השופט") **אינם** מכוסים על-ידי `validate_decision` — WR4 נאכף
+בנקודת-החילוץ (`extract_claims`), WR5 הוא שער-איכות אנושי/agent. הסוכן `legal-qa`
+(ראה [X4-agents.md](X4-agents.md)) מוסיף שערים ידניים מעבר ל-6 הקוד-יים (קול-דפנה,
+שאילתות-קורפוס, צירוף-פסיקה) — `.claude/agents/legal-qa.md`.
+
+---
+
+## 2. שערים אנושיים — היו"ר מכריעה
+
+המערכת מסייעת; ההכרעה היא של היו"ר. שלושה שערים אנושיים מובנים בקוד-הזרימה ואינם ניתנים
+לעקיפה אוטומטית (זהו [INV-G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)).
+
+### 2.1 אישור הלכה (halacha approval)
+
+הלכות מחולצות אוטומטית מפסיקה (`halacha_extractor.py`), אך **נכנסות כ-`pending_review`
+ובלתי-נראות לחיפוש** עד אישור היו"ר:
+
+- **כתיבה:** `db.add_halacha` קובע `review_status = "approved" if auto_approve else
+ "pending_review"` (`db.py:3003`), כאשר `auto_approve` נגזר מסף-ביטחון
+ `HALACHA_AUTO_APPROVE_THRESHOLD` (ברירת-מחדל `0.80`, `config.py:111`). הלכות מתחת לסף
+ נשארות `pending_review`.
+- **שער-האישור:** `halacha_review(halacha_id, status, reviewer="דפנה", …)`
+ (`tools/precedent_library.py:291`, נחשף ב-`server.py:298`) — היו"ר מאשרת/דוחה/עורכת.
+ `status ∈ {pending_review, approved, rejected, published}` (`precedent_library.py:311`).
+- **תור-ההמתנה:** `halachot_pending(limit=100)` (`precedent_library.py:335`) מחזיר את
+ `review_status='pending_review'`.
+- **חשיפה רק לאחר אישור:** החיפוש מסנן `h.review_status IN ('approved','published')`
+ (`db.py:3168` ו-`db.py:3401`) — הלכה שלא אושרה **לעולם** לא עולה בתוצאות.
+
+### 2.2 בחירת תוצאה (outcome selection)
+
+`set_outcome(case_number, outcome, reasoning="")` (`tools/workflow.py:145`,
+`server.py:646`) — היו"ר קובעת `outcome ∈ {rejected, accepted, partial}`
+(`workflow.py:163`). זוהי **הכרעה משפטית**: היא קודמת לכתיבת-הטיוטה וקובעת את מסלול-הדיון
+(ראה [04-analysis-writing.md](04-analysis-writing.md) §3). אין נתיב שבו המערכת בוחרת תוצאה
+לבד — אם לא סופק נימוק, המערכת מציעה כיווני-נימוק (`brainstorm`), אך הבחירה נשארת אנושית.
+
+### 2.3 פידבק היו"ר (chair feedback)
+
+- `record_chair_feedback(case_number, feedback_text, block_id, category, …)`
+ (`tools/workflow.py:348`, `server.py:896`) — מתעד הערת-דפנה; `category` מתוך
+ `{missing_content, wrong_tone, wrong_structure, factual_error, style, other}`
+ (`workflow.py:367`).
+- `list_chair_feedback(case_number, category, unresolved_only=True)`
+ (`tools/workflow.py:393`, `server.py:910`) — שליפה לסקירה.
+
+הפידבק מזין את לולאת-הלמידה ([07-learning.md](07-learning.md)) ואת
+[legal-decision-lessons.md](../legal-decision-lessons.md). זהו שיפוט-אנושי על איכות —
+לעולם לא מוסק או מוחל אוטומטית.
+
+---
+
+## 3. Invariants של התחום
+
+### INV-QA1: אישור הלכה הוא שער אנושי (governance →G10)
+**כלל:** אישור הלכה הוא **הכרעה ידנית של היו"ר**. הלכות שחולצו אוטומטית הן
+`pending_review` עד שהיו"ר מאשרת; **רק הלכות מאושרות** (`approved`/`published`) עולות
+בחיפוש. תור-ההמתנה חייב להיות **נראה** (`halachot_pending`) כדי שאישור-חסר לא יישאר סמוי.
+**מקורות:** NCSC/JTC — *Principles & Practices for AI Use in Courts* (human-in-the-loop) ·
+Council of Europe / CEPEJ (2018, under user control) · Federal Judicial Center —
+*Judicial Writing Manual* (2d ed.) | סטטוס: verified
+**אכיפה:** ברירת-מחדל `pending_review` בכתיבה (`db.py:3003`) + סינון
+`review_status IN ('approved','published')` בכל query (`db.py:3168`, `db.py:3401`) + שער-אישור
+`halacha_review` (`precedent_library.py:291`).
+**הפרה ידועה:** 10/19 הלכות מאושרות — שער-ידני שקוף בלי נראות-backlog; ההפרש התגלה במקרה →
+ממצא ל-[audit](../audit-report.md) (ראה גם [INV-G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)).
+
+### INV-QA2: בחירת-תוצאה ופידבק הם שערים אנושיים (governance →G10)
+**כלל:** **בחירת התוצאה** (`set_outcome`) ו**פידבק-היו"ר** (`record_chair_feedback`) הם
+שערים אנושיים — **לעולם לא אוטומטיים**. המערכת מסייעת (מציעה כיווני-נימוק, מתעדת הערות),
+אך ההכרעה והשיפוט-על-האיכות הם של היו"ר.
+**מקורות:** NCSC/JTC — *Principles & Practices for AI Use in Courts* ("never replace human
+judgment") · Council of Europe / CEPEJ (2018, under user control) · Federal Judicial
+Center — *Judicial Writing Manual* (2d ed.) | סטטוס: verified
+**אכיפה:** `set_outcome` דורש `outcome` מפורש מהיו"ר (`workflow.py:145-165`);
+`record_chair_feedback`/`list_chair_feedback` מתעדים בלבד (`workflow.py:348, 393`) — אין
+מסלול-קוד שמסיק תוצאה או פידבק לבד.
+**הפרה ידועה:** —
+
+### INV-QA3: החלטה לא מיוצאת עם כשל קריטי (governance →G10)
+**כלל:** החלטה **אינה ניתנת לייצוא** כל עוד שער-QA **קריטי** נכשל
+(`claims_coverage` או `structural_integrity`). `export_blocked` חייב להיבדק לפני ייצוא;
+ייצוא בכשל-קריטי הוא הפרה. שערי-`warning` שנכשלים מתועדים אך אינם חוסמים.
+**מקורות:** NCSC/JTC — *Principles & Practices for AI Use in Courts* (controlled, auditable
+AI output) · Council of Europe / CEPEJ (2018, under user control) · Federal Judicial
+Center — *Judicial Writing Manual* (2d ed.) | סטטוס: verified
+**אכיפה:** `export_blocked = critical_failures > 0` (`qa_validator.py:362`); נאכף בשער-הזרימה
+של הסוכן `legal-exporter` ("לעולם אל תייצא בלי `validate_decision` קודם", "בדוק שאין
+כשלים קריטיים" — `.claude/agents/legal-exporter.md:71, 149`). קושר ל-[06-export.md](06-export.md).
+**הפרה ידועה:** `export_docx` (`drafting.py:384`) **אינו** מריץ `validate_decision` בעצמו —
+החסימה היא ברמת-הזרימה/agent, לא hard-block בקוד-הייצוא. פער זה → ראה [§4](#4-current-vs-target--ממצאי-audit) (audit).
+
+### INV-QA4: שערי-התוכן האוטומטיים אוכפים את WR1–WR3 (content →G11)
+**כלל:** שערי-התוכן האוטומטיים מתפעלים את invariants-התוכן: `neutral_background`↔
+[WR1](04-analysis-writing.md#inv-wr1-רקע-ניטרלי-בלוק-ו--עובדות-בלבד) (רקע ניטרלי) ·
+`no_duplication`↔[WR2](04-analysis-writing.md#inv-wr2-ללא-כפילות-בלוק-י-מפנה-לא-חוזר)
+(ללא כפילות) · `claims_coverage`↔[WR3](04-analysis-writing.md#inv-wr3-מענה-לכל-טענה-של-הצד-המפסיד)
+(מענה-לטענות). האכיפה האוטומטית היא **רצפה, לא תקרה** — WR4/WR5 וההבטים העדינים (שיפוט-עקיף,
+כפילות מנוסחת-מחדש) נשארים בשיקול-הדעת האנושי (INV-QA1–QA3).
+**מקור-סמכות:** היו"ר (עו"ד דפנה תמיר) + [04-analysis-writing.md](04-analysis-writing.md)
+(INV-WR1–WR3) + `mcp-server/src/legal_mcp/services/qa_validator.py` (הבדיקות בפועל).
+**אכיפה:** `check_neutral_background` (`qa_validator.py:66`), `check_no_duplication`
+(`qa_validator.py:235`), `check_claims_coverage` (`qa_validator.py:107`).
+**הפרה ידועה:** —
+
+---
+
+## 4. Current vs Target — ממצאי-audit
+
+- **Halacha backlog בלתי-נראה (INV-QA1):** 10/19 הלכות מאושרות; 9 נשארו `pending_review`
+ ולא עלו בחיפוש. השער עבד כשורה — אך חוסר-נראות של ה-backlog הסתיר את הפער עד שהתגלה
+ במקרה. **Target:** מדד-נראות (count `pending_review`) כחלק מבדיקת-בריאות, לא רק
+ `halachot_pending` בדרישה. ראה [audit](../audit-report.md).
+- **שער-ייצוא אכוף-זרימה ולא אכוף-קוד (INV-QA3):** `export_docx` לא קורא ל-`validate_decision`;
+ החסימה תלויה במשמעת הסוכן `legal-exporter`. **Target:** hard-block בתוך `export_docx`
+ (בדיקת `qa_results`/`export_blocked` לפני כתיבת DOCX) כדי שלא יהיה ניתן לעקיפה.
+
+---
+
+## 5. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) —
+ INV-G10 (שערים אנושיים) + INV-G11 + הבחנת שתי-הסמכויות.
+- [04-analysis-writing.md](04-analysis-writing.md) — INV-WR1–WR5 שהשערים האוטומטיים מתפעלים.
+- [06-export.md](06-export.md) — ייצוא DOCX (השלב אחרי המעבר בשער הקריטי).
+- [07-learning.md](07-learning.md) — לולאת פידבק-היו"ר + Hermes שמעדכנת lessons/SKILL.
+- [X4-agents.md](X4-agents.md) — הסוכן `legal-qa` (שערים ידניים נוספים) ו-`legal-exporter`.
+- [X5-audit-provenance.md](X5-audit-provenance.md) — audit-trail לפלטי-AI ועקיבוּת-מקור.
diff --git a/docs/spec/06-export.md b/docs/spec/06-export.md
new file mode 100644
index 0000000..ca3fc8d
--- /dev/null
+++ b/docs/spec/06-export.md
@@ -0,0 +1,168 @@
+# 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 מסומן `` (`_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).
+**הפרה ידועה:** אחרי `revise_draft`/`apply_user_edit`, ה-DOCX המסומן `active_draft_path` הופך
+ל"מקור-האמת" לעריכות-Track-Changes העוקבות (`db.py:185-188`), ו**בלוקי-ה-DB אינם מתעדכנים
+חזרה** — הנתון-הנגזר זוחל למקור-אמת בפועל בלי סנכרון לאחור. **יעד:** או re-sync מהבלוקים, או
+חוזה מפורש ש-`active_draft_path` הוא רק אנקור-revision ולא מקור-תוכן → ראה [§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
+**אכיפה:** `export_blocked = critical_failures > 0` (`qa_validator.py:362`); **נאכף ברמת-
+הזרימה/agent בלבד** — הסוכן `legal-exporter` מחויב להריץ `validate_decision` ולבדוק
+כשלים-קריטיים לפני ייצוא (`.claude/agents/legal-exporter.md:71,149`).
+**הפרה ידועה:** `export_docx` (`drafting.py:384`) **אינו** קורא ל-`validate_decision` בעצמו —
+הוא ניגש ישירות ל-`docx_exporter.export_decision` בלי לבדוק `export_blocked`. החסימה תלויה
+במשמעת-הסוכן ואינה hard-block בקוד-הייצוא → ראה [§5](#5-current-vs-target) (תואם
+[05-qa-review §4](05-qa-review.md#4-current-vs-target--ממצאי-audit)).
+
+---
+
+## 5. Current vs Target
+
+- **שער-ייצוא אכוף-זרימה ולא אכוף-קוד (INV-EX3 / INV-QA3).** אומת בקוד: `export_docx`
+ (`drafting.py:384-419`) קורא ישירות ל-`docx_exporter.export_decision` (`:403`) ללא קריאה
+ ל-`qa_validator.validate_decision` ובלי בדיקת `export_blocked`. החסימה מתקיימת רק כי הסוכן
+ `legal-exporter` מחויב להריץ QA קודם (`legal-exporter.md:71,149`) — אדם/סוכן שיקרא
+ ל-`export_docx` ישירות **יעקוף** את השער. **יעד:** hard-block בתוך `export_docx` — שליפת
+ `qa_results`/`export_blocked` ודחייה לפני כתיבת ה-DOCX, כך שאי-אפשר לעקוף.
+- **`active_draft_path` כ-derived-שזוחל-למקור (INV-EX1).** ה-DOCX נגזר מהבלוקים בייצוא הראשון,
+ אך אחרי עריכה (`revise_draft`/`apply_user_edit`) ה-DOCX הופך ל"מקור-האמת" לעריכות הבאות
+ (`db.py:185-188`) בלי לעדכן את `decision_blocks` חזרה — סטייה אפשרית בין הבלוקים למסמך-החי.
+ **יעד:** חוזה מפורש — או re-sync מהבלוקים, או הגדרת `active_draft_path` כאנקור-revision בלבד
+ (לא מקור-תוכן), עם בדיקת-בריאות לגילוי drift בין הבלוקים ל-DOCX הפעיל.
+
+---
+
+## 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).
diff --git a/docs/spec/07-learning.md b/docs/spec/07-learning.md
new file mode 100644
index 0000000..d557c6c
--- /dev/null
+++ b/docs/spec/07-learning.md
@@ -0,0 +1,189 @@
+# 07 — לולאת הלמידה (Learning Loop)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומפרט כיצד המערכת **לומדת לאורך
+זמן** — מהחלטות סופיות (Hermes), מפידבק-היו"ר, ומצמיחת-הקורפוס — באופן שמזין חזרה את
+הכתיבה ([04-analysis-writing.md](04-analysis-writing.md)) ואת שערי-האיכות
+([05-qa-review.md](05-qa-review.md)). הוא אוכף את
+[INV-G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)
+(שערים אנושיים — אישור היו"ר על כל עדכון-ידע) ואת
+[INV-G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש) /
+כלל-ההנדסה **quality-at-source** (האחריות לאיכות יושבת במקור, לא בטלאי במורד הזרם).
+
+> **⚠ קובץ מעורב — שני מודלי-סמכות** (לפי החוקה §3, §5):
+> - **שער-הממשל** (Hermes מציע — היו"ר מאשרת ידנית; אין auto-commit ל-SKILL/lessons)
+> הוא **invariant הנדסי** במודל הממשל-שיפוטי → נושא `מקורות:` (NCSC/JTC · CEPEJ 2018 ·
+> FJC) + `סטטוס: verified`.
+> - **כלל-ההנדסה quality-at-source** (היכן יושבת האחריות לאיכות-הידע) → invariant הנדסי
+> במודל הנדסת-הנתונים → נושא `מקורות:` (Fowler — Data Mesh / quality-at-source ·
+> DAMA-UK · ISO 8000) + `סטטוס: verified`.
+
+---
+
+## 1. שלוש לולאות-המשנה
+
+הלמידה אינה אירוע יחיד אלא **שלוש לולאות** המתנקזות לאותם מסמכי-ידע מוסמכים
+([legal-decision-lessons.md](../legal-decision-lessons.md),
+[skills/decision/SKILL.md](../../skills/decision/SKILL.md)) ולקורפוסים:
+
+### 1.1 לולאת-Hermes (post-export → הצעה → אישור)
+
+הסוכן [hermes-curator](../../.claude/agents/hermes-curator.md) (adapter `deepseek_local`,
+פרופילים `curator-cmp` / `curator-cmpa`) נקרא **אחרי שדפנה מסמנת קובץ כסופי** ב-UI
+(`POST /api/cases/{case_number}/exports/{filename}/mark-final` → `pc_wake_curator_for_final()`
+ב-`web/paperclip_client.py` → sub-issue + wakeup; **חיבור ישיר מה-UI, לא דרך CEO** —
+`hermes-curator.md:27-35`). הוא:
+
+- **קורא בלבד** את הטקסט הסופי (`case_get_final_text`), `get_style_guide`, ואת
+ `SKILL.md` / `legal-decision-lessons.md` / `corpus-analysis.md` המקומיים
+ (`hermes-curator.md:60-70`).
+- מזהה **3–5 דפוסים/פערים** חדשים, כל ממצא מתויג `[סגנון]` / `[מבנה]` /
+ `[לקסיקון משפטי]` / `[טבלאי]` (`hermes-curator.md:99-108`).
+- **מציע** — comment ב-Paperclip + רישום כל ממצא כ-`decision_lesson` דרך
+ `POST /api/training/corpus/{corpus_id}/lessons` (`source:"curator"`) שמופיע ב-UI
+ תחת הטאב "מה למדנו" (`hermes-curator.md:73-96`).
+- **אינו מעדכן** קבצים בעצמו (skills/, lessons.py, DB) — רק מציע (`hermes-curator.md:125-130`).
+
+### 1.2 לולאת-פידבק-היו"ר (capture → ניתוח שבועי → לקחים)
+
+- **לכידה מובנית:** `record_chair_feedback` שומר הערת-דפנה בטבלת `chair_feedback`
+ (`category ∈ {missing_content, wrong_tone, wrong_structure, factual_error, style,
+ other}`) — `tools/workflow.py:348`, ראה [05-qa-review.md](05-qa-review.md) §2.3.
+- **ניתוח שבועי:** ה-scheduled job `weekly-feedback-analysis` (ראשון 19:00,
+ `plugin-legal-ai/src/manifest.ts:175-179`) מושך `GET /api/chair-feedback/weekly-summary`,
+ ואם יש פריטים — **מעיר את ה-CEO** לעדכן את `legal-decision-lessons.md` עם הלקחים
+ החדשים (`worker.ts:784-837`; הוראת ה-prompt: "הוסף רק לקחים חדשים… קבץ לפי נושא"
+ — `worker.ts:830`).
+- אין פריטים → הג'וב מדלג בשקט (`worker.ts:805`). ל-CEO שמתעורר מ-`weekly-feedback-job`
+ **אין `issueId`** — הוא כותב לקובץ בלבד, לא מפרסם comment ולא סוגר issue
+ (כלל מ-[CLAUDE.md](../../CLAUDE.md) "Scheduled Jobs").
+
+### 1.3 לולאת-צמיחת-הקורפוס (החלטה סופית → קורפוס → אחזור)
+
+החלטה סופית נקלטת לקורפוס-הסגנון (`ingest_final_version` — ראה [06-export.md](06-export.md)
+§ Hermes), ופסיקה/החלטות-ועדה חדשות נקלטות דרך המסלול הקנוני של
+[01-ingest.md](01-ingest.md). כך הקורפוס שמזין את האחזור ([03-retrieval.md](03-retrieval.md))
+**גדל מהפלט עצמו** — והדיון הבא נשען על תקדים עשיר יותר. צמיחה זו כפופה לאותו חוזה-שלמות
+([G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)) כמו כל קליטה.
+
+---
+
+## 2. הלולאה במלואה (הציור)
+
+```
+ ┌──────────────────────────────────────────────────────┐
+ │ │
+ ┌─────────────▼─────────────┐ ┌────────────────────────┐ │
+ │ כתיבה (04) │ ───▶ │ QA + שערים אנושיים (05)│ │
+ │ 12 בלוקים · סגנון דפנה │ │ validate_decision + │ │
+ │ ← lessons.py CONTENT_ │ │ פידבק-היו"ר │ │
+ │ CHECKLISTS · SKILL.md │ └───────────┬────────────┘ │
+ └───────────────────────────┘ │ ייצוא (06) │
+ ▲ ▼ │
+ │ ┌──────────────────────┐ │
+ ┌────────┴──────────────┐ │ סימון "סופי" (UI) │ │
+ │ legal-decision- │ │ mark-final │ │
+ │ lessons.md + SKILL.md │ └───────┬──────────────┘ │
+ │ (מסמכי-ידע מוסמכים) │ │ │
+ └────────▲──────────────┘ ┌──────────┴───────────┐ │
+ │ ▼ ▼ │
+ │ ✋ אישור-יו"ר ידני ┌───────────────┐ ┌────────────────┐│
+ └──────────────────────│ Hermes curator │ │ ingest_final → ││
+ (commit ידני בלבד) │ → הצעות(comment)│ │ קורפוס-סגנון → ┘│
+ └───────────────┘ │ אחזור (03) │
+ ┌───────────────────────────┐ └────────────────┘
+ │ פידבק-היו"ר (05) ──┐ │
+ │ chair_feedback │ │
+ └────────────────────┼───────┘
+ ▼
+ weekly-feedback-analysis (job)
+ │ מעיר CEO
+ ▼
+ עדכון legal-decision-lessons.md ──┐
+ └──▶ (חזרה ל-04 / lessons.py)
+```
+
+הקשר לכתיבה: הלקחים והצ'קליסטים שב-`CONTENT_CHECKLISTS`
+(`mcp-server/src/legal_mcp/services/lessons.py:355`, בורר `get_content_checklist`
+`:509-555`) ו-`get_lessons_for_outcome` (`lessons.py:309`) מוזרקים ל-prompt-הכתיבה לפי
+סוג-ערר ותוצאה — ראה [04-analysis-writing.md](04-analysis-writing.md) §5. כל סגירה של
+לולאה (Hermes או פידבק) שמשנה את `legal-decision-lessons.md` / `SKILL.md` משפיעה ישירות
+על הכתיבה הבאה.
+
+---
+
+## 3. Invariants של התחום
+
+### INV-LRN1: עדכון-ידע דורש אישור-יו"ר ידני — אין auto-commit (governance →G10)
+**כלל:** מנגנוני-הלמידה (Hermes, ניתוח-פידבק שבועי) **מציעים בלבד**. כל שינוי ב-
+[SKILL.md](../../skills/decision/SKILL.md) או ב-[legal-decision-lessons.md](../legal-decision-lessons.md)
+מחייב **בחינה ואישור ידניים של היו"ר/חיים** ואז commit ידני — **לעולם לא auto-committed**.
+Hermes כותב comment + `decision_lesson`, לא קבצים; ה-CEO השבועי כותב לקובץ אך הצעותיו
+מאומתות ידנית לפני קיבוע. זהו פֶּאֶט של [INV-G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)
+על שכבת-הידע: גם הלמידה כפופה לשיקול-הדעת האנושי.
+**מקורות:** NCSC/JTC — *Principles & Practices for AI Use in Courts* (human-in-the-loop;
+never replace human judgment) · Council of Europe / CEPEJ (2018, under user control) ·
+Federal Judicial Center — *Judicial Writing Manual* (2d ed.) | סטטוס: verified
+**אכיפה:** הסוכן read-only על תוכן ו-write רק על comments (`hermes-curator.md:1-3, 125-130`);
+תהליך-האישור — הצעת-curator כ-comment ב-Paperclip → חיים בוחן ומאשר ידנית → commit ל-
+`SKILL.md` ו-`docs/legal-decision-lessons.md` (מ-[CLAUDE.md](../../CLAUDE.md) "Hermes Curator");
+ה-CEO השבועי מתעורר בלי `issueId` וכותב לקובץ בלבד ([CLAUDE.md](../../CLAUDE.md) "Scheduled Jobs").
+**הפרה ידועה:** —
+
+### INV-LRN2: האחריות לאיכות יושבת במקור — quality-at-source (engineering →G4)
+**כלל:** האחריות לאיכות-הידע (לקחים, הלכות, metadata של פריטים מואנדקסים) נאכפת **קרוב
+ככל האפשר לנקודת-היצירה/הקליטה** — בעת ניסוח-ההחלטה, בעת לכידת-הפידבק, ובעת קליטת-פריט —
+**לא** מתוקנת בדיעבד במורד-הזרם (re-OCR, טלאי-קריאה, ניחוש בזמן-חיפוש). פריט-ידע חסר-שלמות
+מסומן ומדווח בנקודת-הכניסה, לא מתקבל בשקט.
+**מקורות:** Martin Fowler — *Data Mesh* (quality-at-source: domain owns data quality at
+the point of creation) · DAMA-UK *Six Primary Dimensions for Data Quality* (2013,
+completeness) · ISO 8000 (Data quality) | סטטוס: verified
+**אכיפה:** חוזה-שלמות בקליטה ([01-ingest.md](01-ingest.md) §2, [02-data-model.md](02-data-model.md))
++ "אין בליעה שקטה" (חוקה §6); לכידת-פידבק מובנית בנקודת-ההערה (`record_chair_feedback`,
+`tools/workflow.py:348`); לקחים נשמרים מבני ולא ad-hoc (`lessons.py`,
+[legal-decision-lessons.md](../legal-decision-lessons.md)).
+**הפרה ידועה:** ראה [INV-G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)
+(ערן סופר 8046/24 אונדקס עם `headnote`/`summary`/`tags` ריקים — שלמות לא נאכפה במקור) →
+ממצא ל-[audit](../audit-report.md).
+
+### INV-LRN3: ידע נלכד באופן מובנה — לא ad-hoc (engineering →G9)
+**כלל:** פידבק ולקחים נלכדים ב**מבנה דטרמיניסטי ועקיב** — `chair_feedback` (עם `category`
+ו-`block_id`), `decision_lessons` (עם `category`/`source`), ו-`CONTENT_CHECKLISTS` בקוד —
+כך שהלמידה **עמידה וניתנת-לביקורת**, לא פזורה בהערות חופשיות. מקור-הלקח (`source:"curator"`
+מול פידבק-יו"ר) משומר לעקיבוּת.
+**מקורות:** ISO 15489-1:2016 (records reliability/authenticity) · DAMA-UK *Six Primary
+Dimensions for Data Quality* (2013) · ISO 8000 (Data quality) | סטטוס: verified
+**אכיפה:** טבלת `chair_feedback` + `record_chair_feedback`/`list_chair_feedback`
+(`tools/workflow.py:348, 393`); `decision_lessons` עם `source`+`category`
+(`hermes-curator.md:79-96`); `CONTENT_CHECKLISTS`/`get_lessons_for_outcome`
+(`lessons.py:355, 309`). עקיבוּת-מקור קושרת ל-[X5-audit-provenance.md](X5-audit-provenance.md).
+**הפרה ידועה:** —
+
+---
+
+## 4. הג'ובים המתוזמנים (תמיכת-תשתית ללולאה)
+
+| Job (`manifest.ts`) | לוח-זמנים | תפקיד בלולאה |
+|---------------------|-----------|---------------|
+| `weekly-feedback-analysis` | ראשון 19:00 (`:175-179`) | מסכם פידבק-יו"ר → מעיר CEO לעדכון `legal-decision-lessons.md` (`worker.ts:784-837`) |
+| `stale-case-reminder` | יומי 08:00 (`:169-172`) | תזכורת על תיקים תקועים 30+ ימים (`worker.ts:710-780`) — היגיינת-תהליך, לא ידע |
+| `sync-case-status` | כל 15 דק' (`:162-166`) | מסנכרן סטטוסי-תיקים legal-ai↔Paperclip (`worker.ts:624`) — תשתית, לא ידע |
+
+רק `weekly-feedback-analysis` הוא חלק מלולאת-הלמידה; שני האחרים הם היגיינת-תהליך/סנכרון.
+
+---
+
+## 5. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) —
+ INV-G10 (שערים אנושיים) + [INV-G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש)
+ (quality-at-source) + כלל-ההנדסה §6.
+- [04-analysis-writing.md](04-analysis-writing.md) — הכתיבה שהלקחים/הצ'קליסטים מזינים (§3, §5).
+- [05-qa-review.md](05-qa-review.md) — שער פידבק-היו"ר (§2.3) שמתחיל את לולאת-הפידבק.
+- [01-ingest.md](01-ingest.md) — קליטה אחידה (quality-at-source) לצמיחת-הקורפוס.
+- [03-retrieval.md](03-retrieval.md) — האחזור שהקורפוס הגדל מזין.
+- [06-export.md](06-export.md) — `mark-final` שמפעיל את Hermes + `ingest_final_version`.
+- [X5-audit-provenance.md](X5-audit-provenance.md) — עקיבוּת-מקור של לקחים (`source`).
+- הסוכן: [.claude/agents/hermes-curator.md](../../.claude/agents/hermes-curator.md).
+- מסמכי-הידע המוסמכים: [legal-decision-lessons.md](../legal-decision-lessons.md) ·
+ [skills/decision/SKILL.md](../../skills/decision/SKILL.md) ·
+ [corpus-analysis.md](../corpus-analysis.md).
diff --git a/docs/spec/README.md b/docs/spec/README.md
new file mode 100644
index 0000000..60226fd
--- /dev/null
+++ b/docs/spec/README.md
@@ -0,0 +1,7 @@
+# ספ המערכת — עוזר משפטי (Living System Spec)
+
+זהו מקור-האמת הקנוני ל"מהו תקין" במערכת. שער-הכניסה: [00-constitution.md](00-constitution.md).
+כל invariant מגובה ב-≥3 מקורות סמכותיים; פריט לא-מאומת מסומן ⚠ UNVERIFIED ומועלה ליו"ר.
+
+מבנה: 00 חוקה · 01–07 מחזור-חיים · X1–X5 חוצי-שלבים. ראה אינדקס מלא בחוקה.
+בסיס-עיצוב: docs/superpowers/specs/2026-05-30-system-spec-design.md
diff --git a/docs/spec/X1-identifiers.md b/docs/spec/X1-identifiers.md
new file mode 100644
index 0000000..8970fb4
--- /dev/null
+++ b/docs/spec/X1-identifiers.md
@@ -0,0 +1,168 @@
+# X1 — מודל המזהים הקנוני (Canonical Identifier Model)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **מזהי הישויות**
+של עוזר משפטי. הוא אוכף את [G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה) (מזהה
+קנוני מנורמל בכתיבה) ומעמיק את [INV-DM2](02-data-model.md#inv-dm2-מזהה-קנוני-יחיד-לכל-ישות)
+מ-[02-data-model.md](02-data-model.md). שני הקבצים חייבים להישאר עקביים: 02 מגדיר *אילו*
+שדות מזהים כל ישות; X1 מגדיר את *הצורה הקנונית* של המזהה ו*איך* הוא מנורמל.
+
+> **TARGET, לא תיאור-מצב.** המודל כאן הוא היעד הקנוני. כל מקום שבו הקוד בפועל
+> (`mcp-server/src/legal_mcp/services/db.py`) סוטה ממנו — מתועד כ-**audit-finding** (§4),
+> תסמין, לא התנהגות תקינה. כל טענה על הקוד הקיים מצוטטת `file:line` ואינה מונחת כתקינה.
+
+---
+
+## 1. הצורה הקנונית של `case_number`
+
+מזהה-התיק (`case_number`) הוא **מספר-תיק מנורמל** — לא מחרוזת-ציטוט, לא תווית-תצוגה. הצורה
+הקנונית מוגדרת ע"י **נרמול בנקודת-הכתיבה** (write-time canonicalization), כך שכל הרשומות
+חולקות פורמט יחיד והשוואה היא תמיד שוויון-מחרוזת מול הצורה הקנונית.
+
+**הנרמול הקנוני (TARGET — מופעל בכתיבה):**
+
+| צעד | פעולה | דוגמה |
+|------|--------|--------|
+| trim | הסרת רווחים מקיפים | `" 8137/24 "` → `"8137/24"` |
+| prefix-strip | הסרת קידומת-הליך לפני הספרה הראשונה ("ערר", "בל\"מ", "עע\"מ") | `"ערר 8137/24"` → `"8137/24"` |
+| separator | איחוד מפריד `/` → `-` | `"8137/24"` → `"8137-24"` |
+
+> **הצורה הקנונית = המספר הרשמי שהוקצה ע"י הוועדה, נשמר ככתבו** — לרבות מקטע-החודש **כשהוקצה**
+> (למשל `8126-03-25`). מספרי-מורשת מסוימים הוקצו **ללא** חודש (למשל `8126-25`); המערכת **אסור**
+> שתמציא או תוסיף (pad) מקטע-חודש שמעולם לא הוקצה. הנרמול-בכתיבה הוא **פורמט-בלבד ודטרמיניסטי**
+> (trim · `/`→`-` · prefix-strip) — הוא **אינו מוסיף ואינו מסיר** מקטע-חודש. הפורמט המועדף
+> מכאן-ואילך כולל את החודש.
+
+> סוג-ההליך (`proceeding_type ∈ {ערר, בל"מ}`) הוא **חלק מהמפתח הקנוני** — לא חלק ממחרוזת
+> ה-`case_number`. הקידומת "ערר"/"בל\"מ" מהכותרת נשללת מהמספר ונשמרת בעמודה ייעודית
+> (`cases.proceeding_type`, `db.py:912`). כך "ערר 8137/24" ו-"בל\"מ 8137/24" הם שתי
+> רשומות מובחנות בעלות אותו `case_number=8137-24` ו-`proceeding_type` שונה.
+
+**נרמול-בכתיבה הוא המנגנון הראשי; התאמה-סלחנית-בקריאה היא נוחות משנית בלבד.** כלל-ההנדסה
+"נרמול לא תיקון-תסמין" (חוקה §6) קובע: מתקנים את הנתון במקור, לא מטליאים בקריאה. אם רשומה
+נשמרה בצורה לא-קנונית — היעד הוא לנרמל אותה במיגרציה/בכתיבה, **לא** לסמוך על מנוע-קריאה
+שיגשר על הפער. ההתאמה-הסלחנית (§3) קיימת כדי לבלוע *קלט-משתמש* רב-צורני (כותרת Paperclip),
+לא כדי לתרץ נתון-מאוחסן לא-קנוני.
+
+---
+
+## 2. שני מרחבי-מזהים: `cases` מול `case_law`
+
+`case_number` מופיע בשתי טבלאות נפרדות עם **שני מרחבי-מזהים שונים** ו**ללא FK חוצה-טבלאות**
+ביניהן. בלבול בין השניים הוא כשל-שורש: תיק חי אינו תקדים, ולהפך.
+
+| ממד | `cases` (תיק חי) | `case_law` (קורפוס פסיקה) |
+|------|------------------|---------------------------|
+| תפקיד | הערר שבטיפול כעת (1xxx/8xxx/9xxx) | תקדים — פסיקה חיצונית **וגם** החלטות-ועדה |
+| מפתח קנוני | `(case_number, proceeding_type)` | `(case_number, source_kind, proceeding_type)` — ראה להלן |
+| אילוץ-ייחודיות | `uq_cases_number_proc` על `(case_number, proceeding_type)` (`db.py:923-924`) | שני partial unique לפי `source_kind` (`db.py:904-909`) |
+| מורשת (הוסרה) | `case_number TEXT UNIQUE NOT NULL` (`db.py:76`), הוסר V15 (`db.py:921-922`) | `case_number TEXT UNIQUE NOT NULL` (`db.py:368`), הוסר V15 (`db.py:902-903`) |
+| FK חוצה | **אין** — `cases` ו-`case_law` הם מרחבים נפרדים | **אין** |
+
+**`case_law` — מזהה מודע-source_kind.** ה-V15 החליפה את `UNIQUE(case_number)` הגלובלי בשני
+partial unique indexes (`db.py:904-909`):
+
+- **`internal_committee`** (החלטות-ועדה פנימיות): `UNIQUE(case_number, proceeding_type)`
+ — `uq_case_law_internal_number_proc`, `WHERE source_kind = 'internal_committee'`.
+- **חיצוני** (`external_upload` / `cited_only` / `nevo_seed`): `UNIQUE(case_number)`
+ — `uq_case_law_external_number`, `WHERE source_kind <> 'internal_committee'`.
+
+לכן המזהה הקנוני של `case_law` הוא הטריפלט **(`case_number` מנורמל, `source_kind`,
+`proceeding_type`)** — עקבי עם [02-data-model §2א](02-data-model.md#2א-case_law--החוזה-הקונקרטי).
+
+**אין הצמדה חוצה-טבלאות.** כשהחלטת-תיק מ-`cases` מצוטטת בהמשך כתקדים, היא נכנסת ל-`case_law`
+כרשומה *חדשה* (`source_kind='internal_committee'`) — לא כ-FK ל-`cases`. שני המרחבים נשארים
+עצמאיים; הגישור ביניהם הוא דרך הקליטה ([01-ingest.md](01-ingest.md)), לא דרך מפתח-זר.
+
+---
+
+## 3. ציטוט מול מזהה — `citation_formatted` הוא תצוגה, לא מפתח
+
+הציטוט-המלא והמזהה-הקנוני הם **שני שדות נפרדים בכוונה**:
+
+- **מזהה קנוני** = `case_number` מנורמל (`8126-03-25`) — המפתח שמשמש לחיפוש, ל-upsert,
+ ולאילוצי-ייחודיות.
+- **ציטוט מעוצב** = `citation_formatted` (`db.py:1070`, V19) — מחרוזת-תצוגה לפי כללי-הציטוט
+ האחיד, למשל: `ערר (ועדות ערר - תכנון ובנייה ת"א-יפו) 81002-01-21 **אברהם אגסי נ' הועדה
+ המקומית** (נבו 25.9.2025)` (`db.py:1067-1068`).
+
+הציטוט הוא **שדה נגזר לתצוגה** — מכיל את המזהה אך גם צדדים, ערכאה, ותאריך-פרסום. הוא **לעולם
+אינו המפתח**. אחסון מחרוזת-ציטוט בשדה-המזהה שובר את הנרמול ([G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה)),
+מערבב תצוגה עם זהות (פוגע ב-1NF — ערך לא-אטומי בשדה-מפתח), ומונע התאמת-שוויון מול המספר
+המנורמל.
+
+---
+
+## 4. Invariants של התחום
+
+### INV-ID1: `case_number` מנורמל בכתיבה — התאמה-סלחנית משנית
+**כלל:** `case_number` מנורמל לצורה קנונית יחידה **בנקודת-הכתיבה** בנרמול **פורמט-בלבד
+ודטרמיניסטי** (trim · prefix-strip · `/`→`-`) — הנרמול **אינו ממציא ואינו מוסיף** מקטע-חודש
+שלא הוקצה. הצורה הקנונית היא **המספר הרשמי שהוקצה** (עם חודש כשהוקצה, למשל `8126-03-25`),
+והשוואה-בקריאה היא שוויון מול הצורה הקנונית. **התאמה-סלחנית-בקריאה היא
+נוחות משנית בלבד** — היא בולעת קלט-משתמש רב-צורני, ואינה תחליף לנרמול-בכתיבה ([G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה),
+כלל-ההנדסה "נרמול לא תיקון-תסמין", חוקה §6).
+**מקורות:** SSOT (Single Source of Truth — normalization principle) · E.F. Codd, First Normal
+Form (CACM 13(6), 1970) · Martin Kleppmann, *Designing Data-Intensive Applications* (O'Reilly,
+2017) | סטטוס: verified
+**אכיפה:** נרמול-בכתיבה בנקודת-הקליטה ([01-ingest.md](01-ingest.md)) + אילוצי-ייחודיות על
+המפתח הקנוני (`uq_cases_number_proc`, `db.py:923-924`; partial unique `case_law`, `db.py:904-909`).
+**הפרה ידועה:** `_normalize_case_number` (`db.py:1196-1211`) מנרמל **בקריאה בלבד** ("tolerant
+lookup", `db.py:1197`), ו-`get_case_by_number` (`db.py:1214-1231`) משווה two-pass (`case_number=$1`
+**OR** `replace(btrim(case_number),'/','-')=$2`, `db.py:1223-1224`) — אין מסלול-כתיבה שמקנן את
+הערך המאוחסן. בנפרד מכך: כשאותו תיק נקלט גם בצורה ללא-חודש וגם עם-חודש (סחף-הזנה, למשל `8126-25`
+מול `8126-03-25` המתייחסים לתיק אחד), הצורה **עם-החודש (הרשמית) היא הקנונית** והרשומה החסרה
+מתואמת אליה — זו **בעיית-תיאום (reconciliation)**, לא חולשה בנרמול (הנרמול אינו אמור לפדד חודש).
+תיאום רשומות-מורשת מעורבות-צורה הוא **פריט ניקיון-נתונים/מיגרציה חד-פעמי** (ראה
+[gap-audit / תת-פרויקט 2](../audit-report.md)), לא אלגוריתם-padding בזמן-ריצה → ממצא
+ל-[audit](../audit-report.md).
+
+### INV-ID2: אין ציטוט-מלא כמזהה — הציטוט שדה-תצוגה נגזר
+**כלל:** אף ישות **אינה** משתמשת במחרוזת-ציטוט-מלאה כמזהה. שדה-המזהה מכיל מספר-תיק מנורמל
+בלבד; הציטוט-המלא חי בשדה ייעודי נפרד (`citation_formatted`, `db.py:1070`) ככלי-תצוגה נגזר
+([G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה), [INV-DM2](02-data-model.md#inv-dm2-מזהה-קנוני-יחיד-לכל-ישות)).
+**מקורות:** SSOT (Single Source of Truth — normalization principle) · E.F. Codd, First Normal
+Form (CACM 13(6), 1970) · Martin Kleppmann, *Designing Data-Intensive Applications* (O'Reilly,
+2017) | סטטוס: verified
+**אכיפה:** הפרדת-שדות ב-schema — מזהה ב-`case_number` (אילוצי-ייחודיות, `db.py:904-909,923-924`),
+ציטוט ב-`citation_formatted` בלבד (`db.py:1070`); נרמול-בכתיבה שדוחה מחרוזת-ציטוט בשדה-המזהה.
+**הפרה ידועה:** החלטות "סופר" נקלטו עם **ציטוט-מלא מאוחסן כ-`case_number`** (שדה-המזהה מכיל
+את מחרוזת-הציטוט במקום מספר-תיק מנורמל) — חיפוש מול המספר המנורמל נכשל, והפער מתגלגל ל-INV-ID1
+(`_normalize_case_number` רק מטליא בקריאה) → ממצא ל-[audit](../audit-report.md).
+
+---
+
+## 5. מצב קיים מול יעד — audit-findings
+
+ההבדלים בין הקוד בפועל ל-TARGET. **אלו תסמינים, לא התנהגויות תקינות.** כל פריט אומת מול `db.py`.
+
+- **נרמול בצד-הקריאה בלבד.** `_normalize_case_number` (`db.py:1196-1211`) מתואר במפורש כ-
+ "tolerant lookup" (`db.py:1197`) — מסיר קידומת לפני הספרה הראשונה, trim, ו-`/`→`-` — אך
+ **אינו מנרמל את הערך המאוחסן**. `get_case_by_number` (`db.py:1214-1231`) בונה סביבו two-pass
+ (exact `OR` normalized, `db.py:1223-1224`). **תסמין:** הנרמול חי כתיקון-תסמין בקריאה ולא
+ כקנוניזציה-בכתיבה, בניגוד ל-[G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה) וכלל-ההנדסה
+ §6. **יעד:** מסלול-כתיבה שמנרמל את `case_number` (פורמט-בלבד: trim/prefix-strip/`/`→`-`,
+ **ללא המצאת חודש**) בנקודת-הקליטה; הקריאה הופכת להשוואת-שוויון פשוטה.
+- **רשומות-מורשת מעורבות-צורה (בעיית-תיאום, לא padding).** כשאותו תיק נקלט גם כ-`8126-25`
+ וגם כ-`8126-03-25` (סחף-הזנה), ה-two-pass אינו מזהה אותם כתיק אחד. **יעד:** תיאום חד-פעמי
+ של הרשומות לצורה הרשמית עם-החודש (הקנונית) במסגרת ניקיון-נתונים/מיגרציה
+ ([gap-audit / תת-פרויקט 2](../audit-report.md)) — **לא** אלגוריתם-padding בזמן-ריצה שממציא חודש.
+- **ציטוט-מלא כ-`case_number` (מורשת).** השדה המקורי `case_number TEXT UNIQUE NOT NULL`
+ (`cases` `db.py:76`, `case_law` `db.py:368`) לא אכף צורה — מה שאפשר אחסון מחרוזת-ציטוט בשדה
+ זה (החלטות "סופר"). הוחלף ב-partial unique מודע-`source_kind` ב-V15 (`db.py:902-909`), אך
+ **ללא ולידציית-צורה בכתיבה**. **יעד:** ולידציית-כתיבה שדוחה ערך שאינו מספר-תיק מנורמל ומפנה
+ ציטוט ל-`citation_formatted`.
+- **שני מרחבי-מזהים, סיכון-בלבול בקוד-קריאה.** `get_case_by_number` (`db.py:1214`) פונה
+ ל-`cases` בלבד; `get_case_law_by_citation` (`db.py:2503`) פונה ל-`case_law` בלבד — נכון, אך
+ שמות-הפונקציות אינם מבדילים את מרחב-המזהים בבירור. **יעד:** תיעוד מפורש (קובץ זה) + עקביות
+ שמות שמשקפת `cases` מול `case_law` כשני מרחבים נפרדים ללא FK.
+
+---
+
+## 6. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md) — [G1](00-constitution.md#inv-g1-מזהה-קנוני-מנורמל-בכתיבה)
+ (מזהה קנוני מנורמל בכתיבה) + כלל-ההנדסה "נרמול לא תיקון-תסמין" (§6).
+- [02-data-model.md](02-data-model.md) — [INV-DM2](02-data-model.md#inv-dm2-מזהה-קנוני-יחיד-לכל-ישות)
+ (מזהה קנוני יחיד) + החוזה הקונקרטי של `case_law`; X1 הוא ה-deep-dive על אותו מזהה.
+- [01-ingest.md](01-ingest.md) — נקודת-הכתיבה שבה הנרמול-בכתיבה צריך להיאכף.
+- [X5-audit-provenance.md](X5-audit-provenance.md) — עקיבוּת-מקור (הציטוט כשדה-תצוגה נגזר).
diff --git a/docs/spec/X2-multi-company.md b/docs/spec/X2-multi-company.md
new file mode 100644
index 0000000..15277d9
--- /dev/null
+++ b/docs/spec/X2-multi-company.md
@@ -0,0 +1,157 @@
+# X2 — מודל רב-החברתי וכללי ה-Sync (Multi-Company & Sync)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **המבנה הרב-חברתי**
+של עוזר משפטי — שתי החברות (CMP/CMPA), 14 הסוכנים, ואיך שינוי-הגדרות מפושט מ-Master ל-Mirror.
+הוא אוכף את [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) (מקור-אמת
+יחיד — אין מסלולים מקבילים מתפצלים) בהקשר של תצורת-סוכנים: שתי החברות הן שתי העתקות של אותה
+מערכת, ואסור להן להתפצל (drift).
+
+> **invariant פרויקטלי-תפעולי.** ה-invariants כאן הם **עובדות על איך המערכת *הזו* מנוהלת**
+> רב-חברתית — לא תאוריה הנדסית כללית ולא תוכן משפטי. אין סמכות חיצונית ל"איך מסנכרנים
+> CMP↔CMPA"; לכן הם נושאים שדה `מקור-סמכות` = הראנבוקים והקוד של הפרויקט עצמו ([CLAUDE.md](../../CLAUDE.md),
+> [HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md), [scripts/sync_agents_across_companies.py](../../scripts/sync_agents_across_companies.py))
+> — **לא** ≥3 מקורות חיצוניים ו**ללא** סטטוס verified/UNVERIFIED. אבל כל invariant **נקשר
+> לעיקרון הגלובלי שהוא משרת**: כלל אי-ה-drift הוא מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים).
+
+---
+
+## 1. שתי החברות: Master מול Mirror
+
+Paperclip מחייב `agents.company_id NOT NULL` — אין סוכנים משותפים. כדי לשרת את שני סוגי
+העררים, המערכת מורצת כ**שתי חברות** נפרדות, כל אחת עם מערך-סוכנים מלא משלה:
+
+| ממד | CMP — **Master** | CMPA — **Mirror** |
+|------|------------------|-------------------|
+| תפקיד | מקור-האמת לתצורת-סוכנים | העתקה מסונכרנת מ-Master |
+| COMPANY_ID | `42a7acd0-30c5-4cbd-ac97-7424f65df294` | `8639e837-4c9d-47fa-a76b-95788d651896` |
+| סוגי תיקים | רישוי ובנייה | היטל השבחה + פיצויים ס'197 |
+| טווח-מספרים | **1xxx** | **8xxx, 9xxx** |
+| CEO Agent ID | `752cebdd-6748-4a04-aacd-c7ab0294ef33` | `cdbfa8bc-3d61-41a4-a2e7-677ec7d34562` |
+
+(המקור: [HEARTBEAT.md §1](../../.claude/agents/HEARTBEAT.md), שורות 38–44; מזהי-החברות מקודדים גם
+ב-[sync_agents_across_companies.py:62-63](../../scripts/sync_agents_across_companies.py).)
+
+**14 סוכנים = 7 × 2.** כל חברה מחזיקה את אותם 7 תפקידי-סוכן (CEO, writer, analyst, researcher,
+qa, proofreader, exporter — ראה [X4-agents.md](X4-agents.md)). מאחר ש-`company_id` הוא `NOT NULL`,
+כל תפקיד מיוצג בשתי **רשומות-סוכן נפרדות** — אחת ל-CMP, אחת ל-CMPA. אין רשומה משותפת.
+
+**Master = CMP, Mirror = CMPA.** התצורה נכתבת ומתוחזקת בחברת ה-Master (CMP, 1xxx), והסנכרון
+הוא **חד-כיווני** CMP → CMPA ([sync...py:1-7,361-362](../../scripts/sync_agents_across_companies.py)).
+
+---
+
+## 2. ניתוב לפי חברה — סינון ב-`company_id`
+
+הזרימה התפעולית נאכפת לפי `$PAPERCLIP_COMPANY_ID` של הסוכן הפועל ([HEARTBEAT.md §1](../../.claude/agents/HEARTBEAT.md)):
+
+- `42a7acd0…` → הסוכן מטפל **רק** בתיקי 1xxx; `8639e837…` → **רק** בתיקי 8xxx/9xxx (שורות 43–44).
+- **אסור** ליצור פרויקט/issue/תוכן לתיק מחוץ לטווח-החברה (שורה 45); issue שמכוון לתיק מחוץ
+ לטווח → סירוב מנומס ב-comment + העֵרת ה-CEO של החברה הנכונה (שורה 46).
+- **CEO שונה לכל חברה** — בחירת ה-CEO ל-wakeup נגזרת מ-`$PAPERCLIP_COMPANY_ID`, **לעולם לא**
+ UUID hardcoded ([HEARTBEAT.md §4ג](../../.claude/agents/HEARTBEAT.md), שורות 143–150).
+- **גבול-חברה נאכף בצד-Paperclip:** wakeup לחברה אחרת נדחה — `Agent key cannot access another
+ company` ([HEARTBEAT.md §4ג](../../.claude/agents/HEARTBEAT.md), שורה 157).
+
+---
+
+## 3. כלל ה-Sync — אחרי כל שינוי-הגדרות ב-Master
+
+> **טריגר:** כל שינוי ב-`adapter_config`, `runtime_config`, `budget_monthly_cents`, או skills
+> של סוכן ב-Master (UI / SQL / API). מקור: סעיף "Cross-company agent sync" ב-[legal-ai/CLAUDE.md](../../CLAUDE.md)
+> וב-[root CLAUDE.md](../../../CLAUDE.md).
+
+הפעולה החובה — קודם בדיקה, אז החלה:
+
+```bash
+PAPERCLIP_BOARD_API_KEY=$(…infisical…) \
+ python ~/legal-ai/scripts/sync_agents_across_companies.py --verify # drift report
+PAPERCLIP_BOARD_API_KEY=$(…) \
+ python ~/legal-ai/scripts/sync_agents_across_companies.py --apply # backup + apply
+```
+
+**מה הסקריפט עושה** (מאומת מול הקוד):
+
+- **חד-כיווני CMP → CMPA**, סינכרון של שדות-תצורה מוגדרים: top-level (`budget_monthly_cents`,
+ `metadata`, `icon`, `title`, `role`), מפתחות `adapter_config` נבחרים (`model`, `effort`,
+ `timeoutSec`, `maxTurnsPerRun`, נתיבי-instructions, `cwd`…), ו-`runtime_config` כ-full-replace
+ ([sync...py:66-75,124-160](../../scripts/sync_agents_across_companies.py)). שדות פר-חברה
+ (`id`, `company_id`, `adapter_type`, `agent_api_keys`, `status`, `spent_monthly_cents`,
+ `permissions`) **אינם** מסונכרנים ([sync...py:24-29](../../scripts/sync_agents_across_companies.py)).
+- **מבוסס-API, לא DB ישיר.** ה-PATCH דרך `PATCH /api/agents/{id}` וה-skills דרך
+ `POST /api/agents/{id}/skills/sync` עם `Authorization: Bearer` ([sync...py:204-237](../../scripts/sync_agents_across_companies.py)).
+- **מסנן skills מקומיים שלא קיימים ב-Mirror.** `desiredSkills` מושוות כ-subset; skills מקומיים
+ של CMP (למשל `local/eba6210d5a/legal-decision`) שלא קיימים ב-CMPA נשמטים עם אזהרה
+ ([sync...py:138-154,194-195](../../scripts/sync_agents_across_companies.py)).
+- **יוצר revisions.** סנכרון skills עובר דרך endpoint ייעודי שמייצר `skill-sync` revision
+ ([sync...py:277-284](../../scripts/sync_agents_across_companies.py)).
+- **idempotent + אל-כשל.** `--verify`/`--dry-run` כברירת-מחדל, גיבוי `pg_dump` לפני `--apply`,
+ pre-flight על קבצי-instructions, ו-re-verify אוטומטי אחרי ההחלה ([sync...py:9,163-173,408-465](../../scripts/sync_agents_across_companies.py)).
+- **מדלג על סוכן עם `adapter_type` שונה בין החברות.** אם ל-Master ול-Mirror `adapter_type`
+ שונה → `SKIPPING`, ללא סנכרון ([sync...py:387-389](../../scripts/sync_agents_across_companies.py)).
+ זו המלכודת ב-INV-MC1 (להלן).
+
+---
+
+## 4. Invariants של התחום (פרויקטלי-תפעולי)
+
+### INV-MC1: תצורת-סוכן ב-Master מפושטת ל-Mirror — אין drift בין החברות
+**כלל:** כל שינוי ב-`adapter_config` / `runtime_config` / `budget_monthly_cents` / skills של
+סוכן בחברת ה-Master (CMP) **חייב** להיות מפושט ל-Mirror (CMPA) דרך סקריפט ה-Sync המבוסס-API
+(`--verify` ואז `--apply`). שתי החברות **לא מתפצלות** — הן שתי העתקות מסונכרנות של אותה תצורה
+(מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) — מקור-אמת
+יחיד, אין מסלולים מקבילים מתפצלים; וכלל-ההנדסה "סימטריה", [חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
+**מקור-סמכות:** סעיף "Cross-company agent sync" ב-[legal-ai/CLAUDE.md](../../CLAUDE.md) +
+ב-[root CLAUDE.md](../../../CLAUDE.md) +
+[scripts/sync_agents_across_companies.py](../../scripts/sync_agents_across_companies.py) +
+[HEARTBEAT.md §1, §4ג](../../.claude/agents/HEARTBEAT.md). (invariant פרויקטלי-תפעולי — ללא
+פרוטוקול ≥3-המקורות; משרת את העיקרון הגלובלי G2.)
+**אכיפה:** סקריפט ה-Sync (idempotent, מבוסס-API, גיבוי+re-verify) — מורץ **ידנית** אחרי כל
+שינוי-תצורה ב-Master. **אין אכיפה אוטומטית** (ראה §5).
+**הפרה ידועה:** הסקריפט **מדלג** על סוכן ש-`adapter_type` שונה בין CMP ל-CMPA
+([sync...py:387-389](../../scripts/sync_agents_across_companies.py)). כשמעבירים סוכן ל-`deepseek_local`
+ב-Master, ה-Mirror נשאר על ה-adapter הישן והסנכרון מדלג עליו — **חובה להחיל את שינוי ה-`adapter_type`
+ידנית בשתי החברות לפני הרצת ה-Sync** ([CLAUDE.md "External adapters — deepseek_local"](../../CLAUDE.md)),
+אחרת נוצר drift שקט באותו סוכן.
+
+### INV-MC2: אין סוכן משותף — רשומה נפרדת לכל חברה
+**כלל:** סוכן **לעולם אינו רשומה משותפת** בין החברות. כל אחד מ-7 התפקידים מיוצג בשתי
+רשומות-סוכן נפרדות (CMP + CMPA), שכן Paperclip מחייב `agents.company_id NOT NULL`. הסנכרון
+מעתיק *ערכי-תצורה* בין שתי רשומות — לא ממזג אותן לרשומה אחת (תואם [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים):
+מקור-אמת יחיד לתצורה, גם כשהיא משוכפלת על פני רשומות).
+**מקור-סמכות:** סעיף "Cross-company agent sync" ב-[legal-ai/CLAUDE.md](../../CLAUDE.md) (14 agents = 7 × 2;
+`agents.company_id NOT NULL`) + [sync...py:4-7,83-103](../../scripts/sync_agents_across_companies.py)
+(שולף מערכי-סוכן נפרדים לכל `company_id`) + [HEARTBEAT.md §1](../../.claude/agents/HEARTBEAT.md).
+(invariant פרויקטלי-תפעולי.)
+**אכיפה:** אילוץ `company_id NOT NULL` בצד-Paperclip; הסקריפט מתאים סוכנים בין החברות לפי
+`name` ולעולם לא יוצר רשומה משותפת ([sync...py:372,383-385](../../scripts/sync_agents_across_companies.py)
+— "we never auto-create").
+**הפרה ידועה:** —
+
+---
+
+## 5. מצב קיים מול יעד — פער אכיפה
+
+ה-Sync הוא **ידני ולא-נאכף**. הסקריפט עצמו בנוי "אל-כשל" (dry-run כברירת-מחדל, גיבוי,
+re-verify), אך **שום מנגנון לא מכריח** הרצה אחרי שינוי-תצורה ב-Master:
+
+- **drift אם שוכחים.** שינוי `adapter_config`/`runtime_config`/budget/skills ב-CMP בלי הרצת
+ `--apply` משאיר את CMPA מאחור — שתי החברות מתפצלות בשקט, בניגוד ל-INV-MC1. **יעד:** טריגר/
+ בדיקת-בריאות תקופתית שמריצה `--verify` ומדווחת drift (היום ההרצה תלויה בזיכרון המפעיל).
+- **מלכודת `adapter_type`-skip.** סוכן עם `adapter_type` שונה בין החברות נשמט מהסנכרון
+ ([sync...py:387-389](../../scripts/sync_agents_across_companies.py)) — ה-`--verify` ידווח
+ `SKIPPING`, אך אם המפעיל לא יחיל את שינוי ה-adapter ידנית בשתי החברות, הסוכן יישאר drifted.
+ **יעד:** אזהרת-SKIPPING שמתבלטת ב-report + צ'קליסט-ידני (כבר מתועד ב-[CLAUDE.md](../../CLAUDE.md)).
+
+---
+
+## 6. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
+ (מקור-אמת יחיד, אין מסלולים מקבילים מתפצלים) + כלל-ההנדסה "סימטריה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
+- [X4-agents.md](X4-agents.md) — מפת 7 תפקידי-הסוכן שמשוכפלים על פני שתי החברות.
+- [X3-integration-deploy.md](X3-integration-deploy.md) — Paperclip (wakeup, ניתוב comments) ו-deploy;
+ ה-wakeup-per-company משלים את הניתוב כאן.
+- [scripts/sync_agents_across_companies.py](../../scripts/sync_agents_across_companies.py) — מימוש ה-Sync.
+- [legal-ai/CLAUDE.md](../../CLAUDE.md) + [root CLAUDE.md](../../../CLAUDE.md) — סעיף
+ "Cross-company agent sync" + "External adapters — deepseek_local" (מלכודת ה-adapter_type).
+- [.claude/agents/HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md) — §1 (סינון-חברה) + §4ג (wake CEO לפי חברה).
diff --git a/docs/spec/X3-integration-deploy.md b/docs/spec/X3-integration-deploy.md
new file mode 100644
index 0000000..299b253
--- /dev/null
+++ b/docs/spec/X3-integration-deploy.md
@@ -0,0 +1,212 @@
+# X3 — אינטגרציה ו-Deploy (Integration & Deploy)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **שני ממדי-התפעול**
+של עוזר משפטי: (א) **האינטגרציה עם Paperclip** — איך המערכת מעירה סוכנים, איך תגובות-משתמש
+מנותבות, ואיך שינוי-סטטוס תיק מתפרסם חזרה; (ב) **מודל ה-Deploy** — שני מודלי-הרצה הדו-קיימים
+על שרת Nautilus (Coolify-Docker מול pm2-מקומי) ומחזור-השינוי של legal-ai.
+
+> **invariant פרויקטלי-תפעולי.** ה-invariants כאן הם **עובדות על איך המערכת *הזו* משתלבת
+> ונפרסת** — לא תאוריה הנדסית כללית ולא תוכן משפטי. אין סמכות חיצונית ל"איך מעירים סוכן
+> Paperclip" או "איך פורסים את legal-ai"; לכן הם נושאים שדה `מקור-סמכות` = הראנבוקים והקוד
+> של הפרויקט עצמו ([root CLAUDE.md](../../../CLAUDE.md), [legal-ai/CLAUDE.md](../../CLAUDE.md),
+> [HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md), זיכרון `reference_paperclip_wakeup`,
+> ו-[web/paperclip_api.py](../../web/paperclip_api.py)) — **לא** ≥3 מקורות חיצוניים ו**ללא**
+> סטטוס verified/UNVERIFIED. אבל כל invariant **נקשר לעיקרון הגלובלי שהוא משרת**: כלל
+> ה-wakeup-דרך-API-בלבד הוא מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
+> (מסלול קנוני יחיד; ה-DB-insert המקביל אסור כי הוא מתפצל מהמסלול שיוצר `heartbeat_run`).
+
+---
+
+## 1. אינטגרציית Paperclip
+
+עוזר משפטי משתלב עם Paperclip בשלושה כיוונים: **wakeup** (legal-ai/אוטומציה → סוכן),
+**ניתוב comments** (משתמש → CEO → סוכן), ו-**webhook יוצא** (legal-ai → פלאגין).
+
+### 1א. Wakeup — תמיד דרך API, לעולם לא דרך DB
+
+הנתיב הקנוני היחיד להערת סוכן הוא `POST /api/agents/{agent-id}/wakeup` עם `payload` המכיל
+`issueId` ([root CLAUDE.md](../../../CLAUDE.md) "Wakeup API"; [legal-ai/CLAUDE.md](../../CLAUDE.md)
+"Wakeup API"; [HEARTBEAT.md §4ד, שורות 152–158](../../.claude/agents/HEARTBEAT.md)):
+
+```bash
+~/legal-ai/scripts/pc.sh POST "/api/agents/$CEO_ID/wakeup" \
+ '{"source":"automation","triggerDetail":"system","reason":"...",
+ "payload":{"issueId":"...","mutation":"comment","commentId":"..."}}'
+```
+
+- **`POST .../wakeup`, לא `/wake`** — שם-הנתיב מדויק ([legal-ai/CLAUDE.md](../../CLAUDE.md)).
+- **חובה `payload.issueId`** — בלעדיו הסוכן מתעורר בלי הקשר (בלי תיק, בלי issue, בלי `cwd`
+ נכון) ([HEARTBEAT.md שורה 156](../../.claude/agents/HEARTBEAT.md)).
+- **אסור `INSERT INTO agent_wakeup_requests` ישיר** — insert ל-DB יוצר רשומת-בקשה בלבד **בלי
+ `heartbeat_run`**, והסוכן **לא יתעורר לעולם** ([HEARTBEAT.md שורה 158](../../.claude/agents/HEARTBEAT.md);
+ זיכרון `reference_paperclip_wakeup`).
+ זהו בדיוק "מסלול מקביל מתפצל" שאסור לפי [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים).
+- **CEO לכל חברה** — מזהה-ה-CEO ל-wakeup נגזר מ-`$PAPERCLIP_COMPANY_ID`, לעולם לא UUID
+ hardcoded; wakeup לחברה אחרת נדחה (`Agent key cannot access another company`)
+ ([HEARTBEAT.md §4ג](../../.claude/agents/HEARTBEAT.md); ראה [X2-multi-company.md §2](X2-multi-company.md)).
+
+### 1ב. ניתוב comments — דרך ה-CEO
+
+תגובת-משתמש על issue ב-Paperclip **אינה** מנותבת ישירות לסוכן-המטרה. הזרימה
+([root CLAUDE.md](../../../CLAUDE.md) "Comment routing"; [legal-ai/CLAUDE.md](../../CLAUDE.md)):
+
+```
+user comment → plugin-legal-ai → ctx.agents.invoke() מעיר CEO
+ → CEO קורא comment, מחליט ניתוב, יוצר issue לסוכן המתאים
+```
+
+- ה-CEO הוא נקודת-הניתוב היחידה — סוכן-משנה לא מקבל עבודה ישירות מ-comment.
+- כל סוכן **חייב** לקרוא comments אחרונים לפני שהוא מתחיל עבודה ([HEARTBEAT שלבים 2b–2c](../../.claude/agents/HEARTBEAT.md)).
+
+### 1ג. Webhook יוצא — עדכון סטטוס תיק לפלאגין
+
+כשסטטוס תיק משתנה דרך `PUT /api/cases/{case_number}`, הבקאנד שולח webhook אסינכרוני
+לפלאגין כ-BackgroundTask, fire-and-forget:
+
+```
+PUT /api/cases/{n} → [BackgroundTask] emit_case_status_webhook()
+ → POST /api/plugins/marcusgroup.legal-ai/webhooks/case-status
+ → plugin-legal-ai/onWebhook() → comment בעברית + CEO wakeup (כש-qa_failed)
+```
+
+מאומת מול הקוד:
+
+- ה-call-site: [web/app.py:2045-2061](../../web/app.py) — ה-webhook מתוזמן רק כש-`old_status
+ != new_status`, ו-`company_id` נגזר מ-prefix מספר-התיק (`1`→licensing, `8/9`→betterment).
+- המימוש: [web/paperclip_api.py:87-117](../../web/paperclip_api.py) — `emit_case_status_webhook`
+ קורא ל-`pc_request("POST", "/api/plugins/.../webhooks/case-status", ...)` עם `timeout=5.0`,
+ בלוק `try/except` שמתעד `logger.warning` ולעולם לא raise (לא חוסם את הקורא).
+- אותו דפוס משרת אירועים נוספים: `emit_missing_precedent_webhook`
+ ([paperclip_api.py:120-165](../../web/paperclip_api.py)) ו-`emit_export_complete_webhook`
+ ([paperclip_api.py:168+](../../web/paperclip_api.py)).
+
+### 1ד. כל קריאת-API דרך helper — לא curl/httpx ישיר
+
+קריאות ל-Paperclip עוברות תמיד דרך helper, לא דרך לקוח גולמי:
+
+- **bash (סוכנים):** `~/legal-ai/scripts/pc.sh [BODY]` — מוסיף אוטומטית
+ `Authorization: Bearer`, `X-Paperclip-Run-Id`, `Content-Type`, ו-base URL
+ ([HEARTBEAT.md §0, שורות 15–32](../../.claude/agents/HEARTBEAT.md); [scripts/pc.sh:8-9,39-40](../../scripts/pc.sh)).
+- **Python (FastAPI):** `from web.paperclip_api import pc_request` — בונה headers דרך
+ `_build_headers` ([paperclip_api.py:47-84](../../web/paperclip_api.py)), משתמש ב-board API key.
+- **למה:** ה-skill הרשמי דורש `X-Paperclip-Run-Id` בכל קריאה משנה issue (audit trail);
+ ה-helper מבטיח עקביות + תאימות ל-board API keys long-lived שלא נושאות JWT claims
+ ([legal-ai/CLAUDE.md](../../CLAUDE.md) "קריאות API — תמיד דרך helper").
+
+---
+
+## 2. מודל ה-Deploy — שני מודלים דו-קיימים
+
+על שרת Nautilus דרים **שני מודלי-הרצה**. ערבוב ביניהם הוא הטעות הנפוצה ביותר
+([root CLAUDE.md](../../../CLAUDE.md) "Deploy architecture"; [legal-ai/CLAUDE.md](../../CLAUDE.md)
+"ארכיטקטורת Deploy").
+
+| ממד | legal-ai (web + web-ui) | Paperclip + legal-chat-service |
+|------|--------------------------|--------------------------------|
+| מודל | **Coolify-managed (Docker)** | **PM2-managed (Node/Python מקומי)** |
+| מחזור-שינוי | commit → push → Gitea Actions build → Coolify redeploy (~2–4 דק') | עריכה → `pm2 restart` |
+| Coolify UUID | `gyjo0mtw2c42ej3xxvbz8zio` | — |
+| build_pack | **`dockerimage`** (לא `dockerfile`) | — |
+| פורטים | Next.js `:3000` (חשוף) + FastAPI `:8000` (פנימי) | Paperclip `localhost:3100`; legal-chat-service `127.0.0.1:8770` (loopback) |
+| הרצה מקומית | **אין** — אין venv של Python על ה-host; אסור `uvicorn`/`next dev` לפרוד | יש; מתחזק דרך pm2 |
+
+### 2א. מחזור-השינוי של legal-ai (Coolify dockerimage)
+
+שינוי קוד ב-`web/` או `web-ui/` **לא נכנס לתוקף** עד שמריצים את כל הצעדים, בסדר:
+
+1. `git commit` + `git push origin main` ל-Gitea.
+2. Gitea Actions בונה image ודוחף ל-registry (`gitea.nautilus.marcusgroup.org/...`).
+3. ה-workflow מפעיל Coolify redeploy דרך API (UUID `gyjo0mtw2c42ej3xxvbz8zio`).
+4. ~2–4 דקות end-to-end. בדיקה: `curl -s https://legal-ai.nautilus.marcusgroup.org/api/health`.
+
+- **אסור** לנסות `uvicorn`/`next dev` לפרוד — הקונטיינר מספק את שני התהליכים; אין סביבת
+ Python על ה-host ([root CLAUDE.md](../../../CLAUDE.md); [legal-ai/CLAUDE.md](../../CLAUDE.md)).
+- **endpoint חדש ≠ זמין ל-UI.** הוספת endpoint ב-`web/app.py` היא תנאי הכרחי אך לא מספיק
+ לצריכה מה-frontend — חובה `npm run api:types` בתוך `web-ui/` כדי לחדש את ה-OpenAPI types
+ ([root CLAUDE.md](../../../CLAUDE.md), שורה 89; [legal-ai/CLAUDE.md](../../CLAUDE.md)).
+
+### 2ב. legal-chat-service ו-host.docker.internal
+
+legal-chat-service (`127.0.0.1:8770`, pm2) הוא גשר host-side שעוטף את `claude` CLI ב-streaming
+לטאב הצ'אט ב-`/training`. הקונטיינר מגיע אליו דרך `host.docker.internal:8770` — ולכן ה-Service
+Definition של legal-ai ב-Coolify **חייב** לכלול `extra_hosts: host.docker.internal:host-gateway`,
+אחרת ה-proxy יקבל `ConnectError` ([root CLAUDE.md](../../../CLAUDE.md); [legal-ai/CLAUDE.md](../../CLAUDE.md)
+"legal-chat-service"). הנחת-היסוד של "קריאות LLM רק ממקומי" נשמרת — ראה
+זיכרון `feedback_claude_session_local_only`.
+
+---
+
+## 3. Invariants של התחום (פרויקטלי-תפעולי)
+
+### INV-INT1: wakeup דרך API בלבד — DB-insert אסור
+**כלל:** הערת סוכן Paperclip **חייבת** לעבור דרך `POST /api/agents/{agent-id}/wakeup` עם
+`payload.issueId`. **אסור** `INSERT INTO agent_wakeup_requests` ישיר — insert ל-DB אינו יוצר
+`heartbeat_run`, ולכן הסוכן **לא יתעורר לעולם**. זהו המסלול הקנוני היחיד; ה-DB-insert הוא
+מסלול-מקביל-מתפצל אסור (מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
+— מקור-אמת/מסלול קנוני יחיד; וכלל-ההנדסה "סימטריה", [חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
+**מקור-סמכות:** "Wakeup API" ב-[root CLAUDE.md](../../../CLAUDE.md) + ב-[legal-ai/CLAUDE.md](../../CLAUDE.md) +
+זיכרון `reference_paperclip_wakeup` +
+[HEARTBEAT.md §4ד, שורות 152–158](../../.claude/agents/HEARTBEAT.md). (invariant פרויקטלי-תפעולי —
+ללא פרוטוקול ≥3-המקורות; משרת את העיקרון הגלובלי G2.)
+**אכיפה:** קריאות-wakeup דרך `pc.sh`/`pc_request` בלבד; `payload.issueId` חובה; בדיקה
+ש-`heartbeat_run` נוצר. **אין אכיפה סכמתית** שתחסום insert ישיר ל-`agent_wakeup_requests` —
+המניעה היא נוהל (ראה §4).
+**הפרה ידועה:** insert ישיר ל-`agent_wakeup_requests` (fallback ישן) → רשומה בלי `heartbeat_run`,
+הסוכן נשאר רדום (זיכרון `reference_paperclip_wakeup`).
+
+### INV-INT2: שינוי-קוד legal-ai נכנס לתוקף רק דרך commit→push→Coolify deploy
+**כלל:** שינוי קוד ב-`web/` או `web-ui/` **לא נכנס לתוקף** עד `git commit` + `git push origin main`
++ build ב-Gitea Actions + Coolify redeploy (build_pack `dockerimage`, UUID `gyjo0mtw2c42ej3xxvbz8zio`).
+**אין** הרצת `uvicorn`/`next dev` מקומית לפרוד. endpoint חדש ב-`web/app.py` דורש גם
+`npm run api:types` ב-`web-ui/` כדי להיחשף ל-UI.
+**מקור-סמכות:** "Deploy architecture" ב-[root CLAUDE.md](../../../CLAUDE.md) (UUID, dockerimage,
+no local uvicorn, api:types) + "ארכיטקטורת Deploy" ב-[legal-ai/CLAUDE.md](../../CLAUDE.md) +
+זיכרון `reference_deployment`.
+(invariant פרויקטלי-תפעולי — ללא פרוטוקול ≥3-המקורות.)
+**אכיפה:** pipeline Gitea Actions → Coolify (אוטומטי בדחיפה ל-main); בדיקה ידנית
+`curl .../api/health` אחרי deploy. **אין** מסלול-פריסה חלופי.
+**הפרה ידועה:** בדיקת שינוי מול הרצה מקומית שלא קיימת — הקוד בפרוד נשאר ישן עד deploy; וכן
+drift אפשרי Infisical↔Coolify env (env לא מתעדכן אוטומטית מ-Infisical, ראה
+זיכרון `feedback_infisical_coolify_drift`).
+
+### INV-INT3: כל קריאת-Paperclip דרך helper — לא curl/httpx ישיר
+**כלל:** קריאות ל-Paperclip API עוברות **תמיד** דרך helper — `pc.sh` (bash/סוכנים) או
+`pc_request` (Python/FastAPI) — ולעולם לא `curl`/`httpx` גולמי. ה-helper מזריק `Authorization`,
+`X-Paperclip-Run-Id` (audit), ו-`Content-Type` באופן עקבי, ותומך ב-board API keys long-lived
+(מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) — מסלול-גישה
+קנוני יחיד ל-Paperclip; ושל [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) —
+audit-trail עקבי).
+**מקור-סמכות:** "קריאות API — תמיד דרך helper" ב-[legal-ai/CLAUDE.md](../../CLAUDE.md) +
+[HEARTBEAT.md §0, שורות 15–32](../../.claude/agents/HEARTBEAT.md) +
+[scripts/pc.sh:8-9,39-40](../../scripts/pc.sh) + [web/paperclip_api.py:47-84](../../web/paperclip_api.py).
+(invariant פרויקטלי-תפעולי — ללא פרוטוקול ≥3-המקורות.)
+**אכיפה:** נוהל + code-review; `pc.sh` ו-`pc_request` הם נקודות-הכניסה היחידות. **אין אכיפה
+אוטומטית** שתחסום `httpx.AsyncClient` ישיר ל-Paperclip בקוד חדש.
+**הפרה ידועה:** —
+
+---
+
+## 4. מצב קיים מול יעד — פער אכיפה
+
+האינטגרציה נשענת על **נוהל**, לא על מחסום-קוד:
+
+- **wakeup (INV-INT1):** אין constraint סכמתי שחוסם insert ישיר ל-`agent_wakeup_requests`;
+ המניעה היא ידע-נוהל ([HEARTBEAT](../../.claude/agents/HEARTBEAT.md)). **יעד:** wrapper/בדיקת-בריאות
+ שמסמן בקשות-wakeup ללא `heartbeat_run` תואם.
+- **helper (INV-INT3):** אין linter/בדיקה שתתפוס `httpx`/`curl` ישיר ל-Paperclip בקוד חדש.
+ **יעד:** כלל-lint שמכריח שימוש ב-`pc_request`/`pc.sh`.
+
+---
+
+## 5. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
+ (מסלול קנוני יחיד) + [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) (audit-trail) +
+ כלל-ההנדסה "סימטריה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
+- [X2-multi-company.md](X2-multi-company.md) — wakeup-per-company + ניתוב לפי `company_id` משלים את §1 כאן.
+- [X4-agents.md](X4-agents.md) — מפת הסוכנים שה-CEO מנתב אליהם comments.
+- [root CLAUDE.md](../../../CLAUDE.md) + [legal-ai/CLAUDE.md](../../CLAUDE.md) — "Wakeup API",
+ "Comment routing", "Deploy architecture", "קריאות API — תמיד דרך helper".
+- [.claude/agents/HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md) — §0 (pc.sh), §4ג–§4ד (wake CEO + payload).
+- [web/paperclip_api.py](../../web/paperclip_api.py) — `pc_request`, `emit_case_status_webhook`.
+- [scripts/pc.sh](../../scripts/pc.sh) — helper ה-bash.
diff --git a/docs/spec/X4-agents.md b/docs/spec/X4-agents.md
new file mode 100644
index 0000000..28a074e
--- /dev/null
+++ b/docs/spec/X4-agents.md
@@ -0,0 +1,140 @@
+# X4 — מפת הסוכנים (Agents Map)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **מי הם הסוכנים**
+של עוזר משפטי, **מה תפקיד כל אחד**, ו**אילו קבצי-ספ כל סוכן חייב לקרוא לפני שהוא פועל**. הוא
+מסייע לסוכן לדעת באיזה ספ לקרוא — ומעגן את [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)
+(המערכת מסייעת; השערים האנושיים הם invariant): כל סוכן קורא את החוקה תחילה ופועל בתחום-אחריותו,
+לא מחליף את שיקול-הדעת האנושי.
+
+> **invariant פרויקטלי-תפעולי.** ה-invariants כאן הם **עובדות על איך הסוכנים של המערכת *הזו*
+> מאורגנים ומופעלים** — לא תאוריה הנדסית כללית ולא תוכן משפטי. אין סמכות חיצונית ל"מי קורא מה
+> לפני שהוא פועל"; לכן הם נושאים שדה `מקור-סמכות` = הראנבוקים וקבצי-הסוכן של הפרויקט עצמו
+> ([HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md), קבצי הסוכן תחת [.claude/agents/](../../.claude/agents/),
+> ו-[החוקה](00-constitution.md)) — **לא** ≥3 מקורות חיצוניים ו**ללא** סטטוס verified/UNVERIFIED.
+> אבל כל invariant **נקשר לעיקרון הגלובלי שהוא משרת**: כלל "קרא-לפני-שתפעל" + תחום-אחריות הם
+> מופע של [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) (סיוע תחת
+> שערים אנושיים) ו-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים).
+
+---
+
+## 1. ההפעלה המשותפת — HEARTBEAT.md
+
+לפני כל עבודה, **כל** סוכן Paperclip עובר את ה-checklist המשותף ב-[HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md):
+זיהוי וסינון-חברה (§1), קריאת comments אחרונים (§1.5, 2b–2c), קריאת `heartbeat-context` עם
+attachments (§1.5ב), וקריאות-API דרך `pc.sh` בלבד (§0). HEARTBEAT גובר על ה-skill הרשמי של
+Paperclip בקונפליקט (project-specific מנצח default), אך אינו מחליף את החוקה — הוא מצטרף אליה:
+קודם החוקה (00) + ספ-התחום, אז ה-HEARTBEAT התפעולי.
+
+**הקשר רב-חברתי.** ל-Paperclip אילוץ `agents.company_id NOT NULL` — אין סוכן משותף. לכן כל אחד
+מ-7 תפקידי הסוכן-הדומייני מיוצג בשתי רשומות (CMP / CMPA), וסוכן מטפל **רק** בתיקי-החברה שלו לפי
+`$PAPERCLIP_COMPANY_ID` (1xxx ל-CMP; 8xxx/9xxx ל-CMPA). ראה [X2-multi-company.md](X2-multi-company.md).
+
+---
+
+## 2. מפת הסוכנים הדומייניים (7 תפקידים × 2 חברות)
+
+הסט המדויק (`ls .claude/agents/`): `HEARTBEAT.md`, `hermes-curator.md`, `legal-analyst.md`,
+`legal-ceo.md`, `legal-exporter.md`, `legal-proofreader.md`, `legal-qa.md`, `legal-researcher.md`,
+`legal-writer.md`. התפקיד נלקח מה-frontmatter של כל קובץ; עמודת "ספ לקרוא" מקשרת תפקיד לקבצי-הספ
+שהוא אוכף/צורך.
+
+| סוכן (קובץ) | תפקיד (מה-frontmatter) | ספ-תחום לקרוא לפני פעולה |
+|-------------|------------------------|---------------------------|
+| [legal-ceo.md](../../.claude/agents/legal-ceo.md) | מנהל תהליך כתיבת החלטות, מתזמר סוכנים, מפקח על התקדמות | **00 + כל הספ** (מתזמר → צריך תמונה מלאה); ניתוב comments → [X3 §1ב](X3-integration-deploy.md) |
+| [legal-proofreader.md](../../.claude/agents/legal-proofreader.md) | מגיה — תיקון שגיאות OCR בטקסט עברי לפני ניתוח | [01-ingest.md](01-ingest.md) (קליטה/טקסט-מחולץ) |
+| [legal-researcher.md](../../.claude/agents/legal-researcher.md) | חוקר תקדימים — פסיקה, מיפוי תכניות, סיכום פרוטוקולים | [03-retrieval.md](03-retrieval.md) (3 קורפוסים, hybrid/RRF, attribution); קליטת-פסיקה → [01-ingest.md](01-ingest.md) |
+| [legal-analyst.md](../../.claude/agents/legal-analyst.md) | מנתח משפטי — חילוץ טענות, ניתוח אסטרטגי, שאלות מחקר | [02-data-model.md](02-data-model.md) + [03-retrieval.md](03-retrieval.md) + [04-analysis-writing.md](04-analysis-writing.md) |
+| [legal-writer.md](../../.claude/agents/legal-writer.md) | כותב — כתיבת בלוקי ההחלטה בסגנון דפנה תמיר | [04-analysis-writing.md](04-analysis-writing.md) + [05-qa-review.md](05-qa-review.md) (כותב מול שערי-QA) |
+| [legal-qa.md](../../.claude/agents/legal-qa.md) | בודק איכות — שלמות, ניטרליות, כיסוי טענות, משקלות לפני ייצוא | [05-qa-review.md](05-qa-review.md) (שערי QA + שערים אנושיים) |
+| [legal-exporter.md](../../.claude/agents/legal-exporter.md) | מייצא — בדיקה סופית, ייצוא DOCX, שמירה מגורסת | [06-export.md](06-export.md) (ייצוא DOCX לפי תבנית דפנה) |
+| [hermes-curator.md](../../.claude/agents/hermes-curator.md) | Knowledge Curator (Hermes) — מנתח החלטות סופיות post-export, מציע עדכוני skills/lessons; read-only על תוכן, write רק על comments | [07-learning.md](07-learning.md) (Hermes · לקחים · לולאת פידבק) |
+
+**הערות על הסט:**
+
+- **CEO = נקודת-הניתוב היחידה.** תגובת-משתמש על issue מעירה את ה-CEO; הוא מחליט ניתוב ויוצר
+ issue לסוכן-המשנה — סוכן-משנה לא מקבל עבודה ישירות מ-comment ([X3 §1ב](X3-integration-deploy.md)).
+- **Hermes — חיבור ישיר, לא דרך CEO.** מופעל מ"סמן כסופי" ב-UI (`mark-final` → `pc_wake_curator_for_final()`),
+ לא מ-CEO; ופועל על מודל `deepseek_local` (לא Claude Code) — ראה [X2 INV-MC1](X2-multi-company.md#inv-mc1-תצורת-סוכן-ב-master-מפושטת-ל-mirror--אין-drift-בין-החברות)
+ למלכודת ה-`adapter_type`-skip בסנכרון. הצעות ה-curator עוברות **אישור-יו"ר ידני** לפני commit
+ ל-`SKILL.md`/`lessons.md` — מופע של [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant).
+- **company_id פר-סוכן.** כל שורה בטבלה מיוצגת פעמיים (CMP + CMPA); ה-CEO לכל חברה שונה
+ ([X2 §1](X2-multi-company.md)). הסוכן פועל רק בטווח-החברה שלו ([X2 §2](X2-multi-company.md)).
+
+---
+
+## 3. סוכני-התהליך (תת-פרויקט 5) — סעיף שמור (RESERVED)
+
+> **סטטוס: מתוכנן, טרם נבנה.** הסעיף הזה הוא **מקום שמור מכוון** עבור סוכני-התהליך שיוגדרו
+> ב**תת-פרויקט 5** — הם **אינם קיימים כיום** ואין לטעות בהם כמופעלים. הם מתועדים כאן כדי
+> שהמפה תהיה שלמה ושכיוון-העבודה יהיה ברור, לא כ-TODO פתוח.
+
+בניגוד לסוכנים הדומייניים (סעיף 2) שמטפלים בתיקי-עררים, **סוכני-התהליך** הם סוכנים שיקראו את
+ספ-המערכת (קבצי 00–07, X1–X5) ו"יעשו את שיעורי-הבית" — יפעלו על *המערכת עצמה*, לא על תיק. שלושה
+תפקידים מתוכננים:
+
+| סוכן-תהליך (מתוכנן) | תפקיד מיועד |
+|----------------------|-------------|
+| **add-feature** | הוספת יכולת חדשה — קורא את הספ הרלוונטי, מאתר את ה-invariants שחלים, ומיישם בלי לשבור G1–G11 |
+| **fix-feature** | תיקון תקלה — מאתר את ה-invariant שהופֵר (מול [audit-report.md](../audit-report.md)) ומתקן במקור, לא בתסמין |
+| **spec-guardian** | שמירת עקביות הספ — מאתר drift בין הקוד לספ ובין קבצי-הספ עצמם; סתירה = ממצא ל-audit |
+
+ההגדרה המלאה (frontmatter, tools, instructions, מיפוי תפקיד→ספ, ושערי-האישור) **תיכתב בתת-פרויקט 5**.
+עד אז — אין רשומות-סוכן, אין wakeup, ואין הסתמכות עליהם בזרימה.
+
+---
+
+## 4. Invariants של התחום (פרויקטלי-תפעולי)
+
+### INV-AG1: כל סוכן קורא את החוקה תחילה, אז את ספ-התחום הרלוונטי — לפני פעולה
+**כלל:** כל סוכן (דומייני או תהליך) **חייב** לקרוא את [00-constitution.md](00-constitution.md)
+תחילה, ואז את ספ-התחום הרלוונטי לתפקידו (לפי הטבלה בסעיף 2), **לפני** שהוא פועל. ה-checklist
+המשותף ב-HEARTBEAT מתבצע בכל ריצה; קריאת-הספ קודמת לעבודה המהותית. סוכן אינו פועל "מהזיכרון" —
+המקור הקנוני להתנהגות הוא החוקה + ספ-התחום (מופע של [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)
+— המערכת מסייעת תחת שערים אנושיים, והסוכן פועל בגבולות שהחוקה מגדירה).
+**מקור-סמכות:** [HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md) (checklist הפעלה משותף) +
+קבצי-הסוכן תחת [.claude/agents/](../../.claude/agents/) (frontmatter + instructions) +
+[00-constitution.md §7](00-constitution.md#7-אינדקס-הספ) (אינדקס הספ — איזה קובץ אוכף איזה invariant).
+(invariant פרויקטלי-תפעולי — ללא פרוטוקול ≥3-המקורות; משרת את העיקרון הגלובלי G10.)
+**אכיפה:** נוהל — ה-checklist ב-HEARTBEAT + הפניות-הספ בקבצי-הסוכן. **אין אכיפה אוטומטית**
+שתכריח קריאת-ספ לפני פעולה (ראה §5 — זה היעד).
+**הפרה ידועה:** —
+
+### INV-AG2: סוכן דומייני פועל רק בתחום-החברה שלו
+**כלל:** סוכן דומייני מטפל **רק** בתיקי-החברה שלו לפי `$PAPERCLIP_COMPANY_ID` (CMP→1xxx;
+CMPA→8xxx/9xxx). אסור ליצור פרויקט/issue/תוכן לתיק מחוץ לטווח; issue מחוץ-לטווח → סירוב מנומס
+ב-comment + העֵרת ה-CEO של החברה הנכונה (מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
+— הפרדה נאכפת לפי `company_id`, אין מסלולים חוצי-חברה מתפצלים; ראה [X2 §2](X2-multi-company.md)).
+**מקור-סמכות:** [HEARTBEAT.md §1](../../.claude/agents/HEARTBEAT.md) (סינון-חברה — כלל-ברזל) +
+קבצי-הסוכן (סעיף "סינון תיקים לפי חברה") + [X2-multi-company.md §2](X2-multi-company.md).
+(invariant פרויקטלי-תפעולי — ללא פרוטוקול ≥3-המקורות; משרת את העיקרון הגלובלי G2.)
+**אכיפה:** סינון-חברה ב-HEARTBEAT + גבול-חברה נאכף בצד-Paperclip (`Agent key cannot access
+another company`, [X2 §2](X2-multi-company.md)).
+**הפרה ידועה:** —
+
+---
+
+## 5. מצב קיים מול יעד — חיווט הספ לסוכנים
+
+ספ-המערכת (קבצי 00–07, X1–X5) הוא **חדש** — קבצי-הסוכן וה-HEARTBEAT עדיין **אינם מפנים אליו**
+במפורש; הם מפנים ל-CLAUDE.md, למסמכי-`docs/` הישנים, ול-skills. זהו פער אמיתי:
+
+- **קיים:** HEARTBEAT אוכף checklist הפעלה (סינון-חברה, comments, pc.sh) אך **לא** מחייב קריאת
+ `00-constitution.md` או ספ-התחום.
+- **יעד:** לחווט את HEARTBEAT וקבצי-הסוכן כך שיחייבו במפורש את INV-AG1 — קריאת החוקה + ספ-התחום
+ הרלוונטי (לפי הטבלה בסעיף 2) לפני עבודה מהותית. זהו תנאי-מוקדם לסוכני-התהליך (סעיף 3), שכל
+ עבודתם היא "לקרוא את הספ ולעשות שיעורי-בית".
+
+---
+
+## 6. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md) — [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant)
+ (שערים אנושיים) + [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)
+ (מקור-אמת/הפרדה) + [§7 אינדקס הספ](00-constitution.md#7-אינדקס-הספ).
+- [X2-multi-company.md](X2-multi-company.md) — 14 סוכנים = 7 × 2, `company_id` פר-סוכן, כללי sync.
+- [X3-integration-deploy.md](X3-integration-deploy.md) — wakeup, ניתוב comments דרך CEO, webhooks.
+- ספ-התחום שכל סוכן צורך: [01-ingest.md](01-ingest.md), [02-data-model.md](02-data-model.md),
+ [03-retrieval.md](03-retrieval.md), [04-analysis-writing.md](04-analysis-writing.md),
+ [05-qa-review.md](05-qa-review.md), [06-export.md](06-export.md), [07-learning.md](07-learning.md).
+- [.claude/agents/HEARTBEAT.md](../../.claude/agents/HEARTBEAT.md) + קבצי-הסוכן תחת
+ [.claude/agents/](../../.claude/agents/) — frontmatter (תפקיד) + instructions (סינון-חברה, זרימה).
diff --git a/docs/spec/X5-audit-provenance.md b/docs/spec/X5-audit-provenance.md
new file mode 100644
index 0000000..b3fe9a6
--- /dev/null
+++ b/docs/spec/X5-audit-provenance.md
@@ -0,0 +1,163 @@
+# X5 — Audit-Trail ועקיבוּת-מקור (Provenance)
+
+קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומגדיר את **חוזה העקיבוּת וה-audit-trail (TARGET)**
+של עוזר משפטי: (א) כל **תוצר מסיוע-AI** (בלוק-טיוטה, תוצאת-אחזור, הצעת-curator) מתעד **מה הפיק אותו**
+(מקורות/נתונים/מודל); (ב) כל **סמכות מצוטטת** בהחלטה **פתירה חזרה לקורפוס**; (ג) **שלמות-הרשומה
+לאורך זמן** — החלטה/רשומה שלמה ובלתי-משתנה אלא דרך **שינויים עקיבים ומיוחסים** (היסטוריית git +
+Track Changes). הקובץ אוכף את
+[INV-G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) (עקיבוּת + audit-trail) ואת
+[INV-G5](00-constitution.md#inv-g5-metadata-מלא--הפרדת-קורפוס-נאכפת-בכל-query) (attribution באחזור).
+
+> **TARGET, לא תיאור-מצב.** היכן שהקוד בפועל סוטה מהיעד — מתועד כ-**audit-finding** ([§5](#5-current-vs-target--ממצאי-audit)),
+> תסמין לתיקון, לא התנהגות תקינה. כל טענה על הקוד מצוטטת `file:line`.
+
+כשל-השורש שהקובץ מייבש: **קיימים רכיבי-עקיבוּת נקודתיים** (commit git לפלטים · `model_used` לכל בלוק ·
+`decision_paragraphs.citations` · גרף-ציטוטים · telemetry של חיפושים), אך **אין רשומת-provenance
+מאוחדת מקצה-לקצה** שמקשרת בלוק-החלטה → קטעי-הקורפוס/הגנרציות שהפיקו אותו, ו**טבלת ה-`audit_log`
+אינה מתועדת בפועל** לרוב פעולות ה-AI.
+
+---
+
+## 1. שלוש שכבות העקיבוּת (TARGET)
+
+| שכבה | מה צריך להירשם | היכן (קיים / יעד) |
+|------|-----------------|---------------------|
+| **A — provenance של תוצר-AI** | לכל בלוק-טיוטה/תוצאת-אחזור/הצעת-curator: מודל, סוג-גנרציה, וקטעי-המקור (chunks/precedents) שהוזנו | קיים חלקית: `decision_blocks.model_used/generation_type/temperature` (`db.py:326-328`); **חסר** קישור בלוק→קטעי-מקור |
+| **B — עקיבוּת ציטוט→קורפוס** | כל סמכות מצוטטת פתירה ל-`case_law_id`/`document_id` + locator | קיים: `decision_paragraphs.citations` JSONB `[{case_law_id,text,type}]` (`db.py:343`); גרף `precedent_internal_citations` (`db.py:937-947`) |
+| **C — שלמות-רשומה לאורך זמן** | החלטה/מסמך שלם ובלתי-משתנה אלא דרך שינוי עקיב ומיוחס | קיים: commit git לכל פלט (`git_sync.commit_and_push`); Track Changes ב-revisions ([06-export §3](06-export.md#3-רישום-הגרסה--active_draft_path--git)) |
+
+---
+
+## 2. רכיבי-העקיבוּת הקיימים (מאומת `file:line`)
+
+1. **קיבוע-פלט ב-git.** כל כתיבת-DOCX/עדכון-תיק מקובעת בהיסטוריית-git של תיקיית-התיק:
+ `export_docx` (`drafting.py:408`), `export_interim_draft` (`drafting.py:536`),
+ `apply_user_edit` (`drafting.py:582`), `revise_draft` (`drafting.py:695`), עדכון-תיק
+ (`cases.py:387`), הוספת-מסמך (`documents.py:86`) — כולם `git_sync.commit_and_push(...)`
+ (`git_sync.py:75`). זו שכבת ה-audit-trail של **שלמות-הפלט** (שכבה C).
+2. **provenance של מודל לכל בלוק.** `decision_blocks` נושא `model_used` / `generation_type` /
+ `temperature` (`db.py:326-328`), הנכתבים ב-upsert של ה-block-writer
+ (`block_writer.py:1017-1034`, `_build_result` `:400-407`). מתעד **איזה מודל** הפיק את הבלוק
+ (שכבה A — חלקי).
+3. **עקיבוּת ציטוט ברמת-סעיף.** `decision_paragraphs.citations` (`db.py:343`) שומר
+ `[{case_law_id, text, type}]` — כל ציטוט בסעיף מצביע ל-`case_law` (שכבה B). telemetry
+ ממנף זאת ל-"cited == relevant" (`telemetry.py:18-23`).
+4. **גרף-ציטוטים פנימי.** `precedent_internal_citations` (`db.py:937-947`) רושם קשת
+ החלטה→החלטה מצוטטת (resolved ל-`case_law` או stub); נחשף דרך `extract_internal_citations` /
+ `list_internal_citations` / `list_incoming_citations` (`citations.py:40,81,112`).
+ ON CONFLICT DO NOTHING → idempotent (`citations.py:54`).
+5. **locator פתיר בכל תוצאת-אחזור.** כל span מוחזר נושא מזהה-מקור + locator
+ ([03-retrieval INV-RET5](03-retrieval.md#inv-ret5-כל-span-מוחזר-עקיב-למקורו), `search.py:77-86,322-343`);
+ הלכות נושאות `supporting_quote` (`db.py:652`) + `page_number` (`db.py:631,711,729`).
+6. **telemetry של חיפושים.** `telemetry.log_search_bg` (ב-search.py) → מפעיל את `log_search` האסינכרוני → `search_logs`
+ (`telemetry.py:105,161`, `search.py:62,118,190,271`) רושם query/practice_area/top_case_law_ids —
+ תצפית על מה נשלף, fire-and-forget (`telemetry.py:8-12,100-101`).
+7. **לקחים ופידבק מיוחסים.** `decision_lessons.source` (`db.py:208`: manual/curator/chair/
+ style_analyzer) ו-`chair_feedback.lesson_extracted`/`applied_to` (`db.py:458-459`) מתעדים את
+ **מקור** הלקח ([07-learning.md](07-learning.md)).
+8. **טבלת `audit_log` (פעולה כללית).** `log_action(action, case_id, document_id, details, user)` (עמודת-DB: `actor`)
+ (`audit.py:18-44`) → `audit_log` (`db.py:159-167`, אינדקסים `:168-170`). קיימת, אך נכתבת
+ כיום כמעט-ורק ב-`case_subtype_override` (`cases.py:203`) — ראה [§5](#5-current-vs-target--ממצאי-audit).
+
+---
+
+## 3. Invariants של התחום
+
+### INV-AUD1: כל תוצר מסיוע-AI מתעד את ה-provenance שלו (→G9)
+**כלל:** כל תוצר שנוצר בסיוע-AI — בלוק-טיוטה, תוצאת-אחזור, הצעת-curator — **רושם את מקורו**:
+**איזה מודל** הפיק אותו, **באיזה סוג-גנרציה**, ו**אילו קטעי-מקור** (chunks/precedents/מסמכי-תיק)
+הוזנו אליו. הרשומה ניתנת-לביקורת בדיעבד (מי/מתי/ממה).
+**מקורות:** Council of Europe / CEPEJ — *European Ethical Charter on AI in judicial systems*
+(2018, transparency/traceability + user-control) · NCSC/JTC — *Principles & Practices for AI Use
+in Courts* (auditable AI output) · ISO 15489-1:2016 (records authenticity — metadata about
+creation) | סטטוס: verified
+**אכיפה:** `decision_blocks.model_used/generation_type/temperature` בכל upsert של בלוק
+(`block_writer.py:1017-1034`); telemetry על כל חיפוש (`telemetry.py:105`); **יעד נוסף:** קישור
+מפורש בלוק→קטעי-מקור (provenance edges) + כתיבת `audit_log.log_action` לכל גנרציה. אוכף את
+[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai).
+**הפרה ידועה (GAP):** ה-provenance קיים **חלקית** — `model_used` נרשם לכל בלוק, וה-commit ב-git
+מקבע פלטים, אך **אין רשומה מאוחדת** שמקשרת בלוק-החלטה לקטעי-הקורפוס/הגנרציות שהזינו אותו, וטבלת
+`audit_log` כמעט-ולא נכתבת לפעולות-AI (רק `case_subtype_override`, `cases.py:203`) → יעד
+([§5](#5-current-vs-target--ממצאי-audit)).
+
+### INV-AUD2: רשומה שמורה שלמה ובלתי-משתנה אלא דרך שינוי עקיב ומיוחס (→G9, שלמות-רשומה)
+**כלל:** החלטה/רשומה שמורה היא **שלמה ובלתי-משתנה** — כל שינוי בה נעשה רק דרך **מנגנון עקיב
+ומיוחס** (commit git עם הודעה + actor, או Track Changes מיוחסות), ולא דרך דריסה שקטה. ניתן
+לשחזר את מצב-הרשומה בכל נקודת-זמן ולזהות מי שינה מה ומתי.
+**מקורות:** ISO 15489-1:2016 (§5.2.2 — integrity: records protected against unauthorized
+alteration; אמינות/שלמות-רשומה) · Council of Europe / CEPEJ (2018, traceability) · DAMA-UK —
+*Six Primary Dimensions for Data Quality* (2013, consistency/integrity over time) | סטטוס: verified
+**אכיפה:** קיבוע git לכל פלט (`git_sync.commit_and_push` — `drafting.py:408,536,582,695`;
+`cases.py:387`; `documents.py:86`) עם הודעה תיאורית; Track Changes ב-revisions עוקבות
+([06-export §3](06-export.md#3-רישום-הגרסה--active_draft_path--git)); `decision_blocks` עם מפתח
+קנוני `UNIQUE(decision_id, block_id)` (`db.py:333`) ו-`updated_at`. אוכף את
+[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai).
+**הפרה ידועה:** עריכת-DOCX (`revise_draft`/`apply_user_edit`) הופכת את `active_draft_path` למקור-
+בפועל **בלי לעדכן את בלוקי-ה-DB חזרה** — הנתון-הנגזר זוחל למקור-אמת ושלמות ה-DB מול המסמך-החי
+נחלשת ([06-export INV-EX1](06-export.md#inv-ex1-ייצוא-דטרמיניסטי-ומשוחזר-מהבלוקים--docx-הוא-נתון-נגזר-g2)) → ממצא ל-[audit](../audit-report.md).
+
+### INV-AUD3: כל סמכות מצוטטת פתירה חזרה לקורפוס (→G5)
+**כלל:** כל סמכות-משפטית המצוטטת בהחלטה (פסק-דין, הלכה, מסמך-תיק) **פתירה לרשומת-מקור בקורפוס**
+דרך locator יציב — `case_law_id`/`document_id` + מזהה-עמוד/chunk/quote. ציטוט שאינו פתיר אינו
+תקין; הוא נחסם או מסומן לאימות-יו"ר. זהו צד-ה-attribution של [INV-RET5](03-retrieval.md#inv-ret5-כל-span-מוחזר-עקיב-למקורו).
+**מקורות:** Pinecone — *Implement multitenancy* (metadata-locator לכל פריט מואנדקס) · RAG
+attribution (Lewis et al., 2020, NeurIPS — pinned/non-leaking provenance) · ISO 8000 (Data
+quality — completeness/identifiability) | סטטוס: verified
+**אכיפה:** `decision_paragraphs.citations` `[{case_law_id,text,type}]` (`db.py:343`); גרף
+`precedent_internal_citations` (`db.py:937-947`) פותר ציטוט ל-`case_law` קיים או שומר stub;
+פורמטרי-האחזור מצרפים מזהה+locator (`search.py:77-86,322-343`). אוכף את
+[G5](00-constitution.md#inv-g5-metadata-מלא--הפרדת-קורפוס-נאכפת-בכל-query).
+**הפרה ידועה (GAP):** הקישור קיים ברמת-הסעיף (`decision_paragraphs.citations`), אך **אין אכיפה**
+שכל ציטוט בטקסט-הבלוק אכן מקושר לרשומת-קורפוס; ציטוט שהמודל ייצר בלי locator יכול לעבור בלי
+חסימה אוטומטית — אימות נשען על שער-היו"ר ([05-qa-review](05-qa-review.md)) → יעד.
+
+---
+
+## 4. רשומת-ה-provenance המאוחדת (TARGET)
+
+היעד שמאחד את שלוש השכבות: לכל **בלוק-החלטה** נשמר, מעבר ל-`model_used` הקיים, **קישור לקטעי-
+המקור** שהוזנו לגנרציה (chunk-ids/`case_law_id`s שהוחזרו מהאחזור והוצגו ל-writer) — כך שניתן לענות
+"מאיזו פסיקה/מסמך נולד המשפט הזה?". המנגנון הקנוני המוצע: כתיבת `audit_log.log_action`
+(`audit.py:18`) בכל גנרציה (`action="write_block"`, `details={model, generation_type, source_chunk_ids,
+retrieved_case_law_ids}`) — הטבלה כבר תומכת ב-`details JSONB` + `actor` + `case_id`/`document_id`
+(`db.py:159-167`). זה ממיר את ה-audit_log מ"כמעט-ריק" ל-audit-trail מקצה-לקצה, בלי טבלה חדשה
+(תואם כלל-ההנדסה "סימטריה" — הרחבת מסלול קיים, [חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)).
+
+---
+
+## 5. Current vs Target — ממצאי-audit
+
+ההבדלים בין הקוד בפועל ל-TARGET. **אלו תסמינים, לא התנהגויות תקינות.** כל פריט אומת מול הקוד.
+
+- **`audit_log` קיימת אך כמעט-ולא נכתבת (INV-AUD1).** `log_action` (`audit.py:18-44`) ו-טבלת
+ `audit_log` (`db.py:159-167`) מוכנות, אך הקריאה היחידה בפועל היא `case_subtype_override`
+ (`cases.py:203`) — אין רישום ל-`upload`/`extract_claims`/`write_block`/`export` (למרות ש-docstring
+ של `log_action` מונה אותם, `audit.py:28`). **תסמין:** אין audit-trail אחיד "מי עשה מה מתי" לרוב
+ פעולות-ה-AI. **יעד:** קריאת `log_action` בכל פעולה משנה-מצב, כולל גנרציות.
+- **אין קישור בלוק→קטעי-מקור (INV-AUD1).** `decision_blocks` מתעד `model_used`/`generation_type`
+ (`db.py:326-327`) אך **לא** את ה-chunks/precedents שהוזנו לגנרציה. **תסמין:** אי-אפשר לשחזר מאיזו
+ פסיקה/מסמך נגזר בלוק ספציפי. **יעד:** רשומת-provenance מאוחדת ([§4](#4-רשומת-ה-provenance-המאוחדת-target)).
+- **ציטוט→קורפוס לא נאכף אוטומטית (INV-AUD3).** `decision_paragraphs.citations` (`db.py:343`)
+ תומך בקישור, אך אין בדיקה שכל ציטוט בטקסט אכן פתיר ל-`case_law`. **תסמין:** ציטוט שהמודל ייצר בלי
+ locator יכול לעבור. **יעד:** ולידציה שכל citation בעלת `case_law_id` פתיר, אחרת flag לאימות-יו"ר.
+- **שלמות ה-DB מול ה-DOCX-החי נחלשת אחרי עריכה (INV-AUD2).** אחרי `revise_draft`/`apply_user_edit`,
+ `active_draft_path` הופך מקור-בפועל בלי re-sync לבלוקים (`db.py:189`;
+ [06-export INV-EX1](06-export.md#inv-ex1-ייצוא-דטרמיניסטי-ומשוחזר-מהבלוקים--docx-הוא-נתון-נגזר-g2)).
+ **יעד:** re-sync מהבלוקים או חוזה מפורש + health-check לגילוי drift.
+- **telemetry בולעת שגיאות בשתיקה (תיעוד, לא הערכה).** `log_search` swallow מכוון
+ (`telemetry.py:100-101`) כדי שלא להפיל חיפוש — תקין כ-fire-and-forget, אך אינו audit-trail
+ מהימן (רשומה עלולה ללכת לאיבוד בשקט). תואם את העיקרון "אין בליעה שקטה" רק כי זו telemetry-תצפית,
+ לא רשומת-שלמות; ה-audit-trail המהימן הוא git ([§2.1](#2-רכיבי-העקיבוּת-הקיימים-מאומת-fileline)).
+
+---
+
+## 6. הפניות-אחיות
+
+- [00-constitution.md](00-constitution.md) — [INV-G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai)
+ (עקיבוּת + audit-trail) · [INV-G5](00-constitution.md#inv-g5-metadata-מלא--הפרדת-קורפוס-נאכפת-בכל-query) (attribution).
+- [03-retrieval.md](03-retrieval.md#inv-ret5-כל-span-מוחזר-עקיב-למקורו) — INV-RET5 (locator פתיר בכל span — בסיס ל-INV-AUD3).
+- [06-export.md](06-export.md#inv-ex2-עקיבוּת-מקור-נשמרת-בהחלטה-המיוצאת-g9) — INV-EX2 (עקיבוּת בפלט) + commit git (INV-AUD2).
+- [05-qa-review.md](05-qa-review.md) — שער-היו"ר שמאמת ציטוטים (משלים את INV-AUD3).
+- [02-data-model.md](02-data-model.md) — `decision_blocks`/`decision_paragraphs`/`case_law` (הישויות שעליהן נשמרת ה-provenance).
+- [07-learning.md](07-learning.md) — `decision_lessons.source` + `chair_feedback` (מקור הלקחים).
+- [01-ingest.md](01-ingest.md) — קליטה שמייצרת את הקטעים שאליהם פותרים ציטוטים.
diff --git a/docs/spec/gap-audit.md b/docs/spec/gap-audit.md
new file mode 100644
index 0000000..6537de4
--- /dev/null
+++ b/docs/spec/gap-audit.md
@@ -0,0 +1,125 @@
+# Gap-Audit — פערים בין המערכת הקיימת ל-spec
+
+מסמך זה הוא **מפת-הפערים הקנונית** בין המערכת הקיימת (קוד ב-`web/`, `mcp-server/`,
+`scripts/`) לבין ה-invariants שב-[`docs/spec/`](README.md). הוא תוצר של תת-פרויקט 2
+(מיפוי-פערים), ומובחן מ-[`docs/audit-report.md`](../audit-report.md) הישן: ה-audit הוא
+דוח-מצב נקודתי, וזה ה-gap-map שמקשר כל ממצא ל-invariant מופר וליחידת-תיקון.
+
+**איך הופק:** סקירה חוצת-קבצים של כל קבצי-הספ (00 + 01–07 + X1–X5) מול הקוד הקיים,
+30.5.2026. כל ממצא נושא: `invariant מופר` (ה-G*/INV-* שהוא סותר), הערכת-`severity`,
+`קבצים מושפעים` (file:line), ו-`תיקון מוצע`.
+
+**הערה על severity/priority:** דירוג ה-severity להלן הוא הערכה הנדסית (לפי סיכון
+לשלמות-נתונים, דליפה חוצת-קורפוס, ועקיפת שער אנושי). **קביעת ה-priority בפועל —
+מה לתקן ראשון — היא של היו"ר.** ה-severity מנמק; הוא אינו מכריע.
+
+---
+
+## 23 הממצאים
+
+| ID | כותרת | invariant מופר | severity | קבצים מושפעים (file:line) | תיקון מוצע |
+|----|-------|----------------|----------|---------------------------|------------|
+| GAP-01 | שני מסלולי ingest מקבילים שמתפצלים | INV-ING1, G2 | High | `precedent_library.py:88`, `internal_decisions.py:73` | מסלול-קליטה קנוני יחיד; ישויות-אחיות חולקות פייפליין |
+| GAP-02 | ingest פנימי מדלג על חילוץ metadata | INV-ING3, DM1, RET2 | Critical | `internal_decisions.py:208` | להוסיף `request_metadata_extraction` לכל סוג; חוסם indexing ריק |
+| GAP-03 | אין upsert דטרמיניסטי על מזהה קנוני | INV-ING2, G3 | Critical | `precedent_library.py`, `internal_decisions.py` | upsert על מפתח קנוני — קליטה חוזרת = update לא duplicate |
+| GAP-04 | ולידציית-enum א-סימטרית | INV-G4 | Medium | `precedent_library.py:131-134` | להחיל אותה ולידציית practice_area/source_type בשני המסלולים |
+| GAP-05 | staging/derivation/citation-guard/multimodal/fallback א-סימטריים | INV-ING1, G2 | High | `01-ingest §4` (שני המסלולים) | מיזוג כל שלבי-העיבוד למסלול הקנוני האחד |
+| GAP-06 | case_number מנורמל בקריאה בלבד | INV-G1, ID1 | High | `db.py:1196-1211` | נרמול בנקודת-הכתיבה; `8126-25`→canonical |
+| GAP-07 | מספרי-תיק מעורבים (חודש/חסר) — reconciliation חד-פעמי | INV-ID1 | High | data (cases, case_law) | מיגרציה: canonical = הצורה הרשמית שהוקצתה [chair-confirmed] |
+| GAP-08 | ציטוט-מלא נשמר כ-case_number | INV-DM2, ID2 | Medium | data (legacy pre-V15) | ניקוי: ציטוט = שדה-תצוגה נגזר, לא מזהה |
+| GAP-09 | `embedding` אינו GENERATED (בניגוד ל-tsvectors) | INV-DM3, RET, G6 | High | schema (chunks/case_law) | re-index באכיפה — טריגר או GENERATED-equivalent בשינוי תוכן |
+| GAP-10 | דליפת הלכה חוצת-קורפוס | INV-RET1, G5 | Critical | `db.py:3168`, `db.py:3401`, JOINs `3236-3238`/`3475-3477` | להוסיף `cl.source_kind` ל-halacha_filters |
+| GAP-11 | אין eval harness / gold-set מתויג | INV-RET4, G8 | High | `telemetry.log_search_bg` (היחיד) | להקים eval harness + gold-set; precision/recall נמדד |
+| GAP-12 | search_decisions מזהיר אך לא חוסם practice_area חסר | INV-RET, G5 | High | `search.py:45-49`, `search.py:172-176` | לחסום query בלי practice_area — ערבוב-תחום אסור |
+| GAP-13 | אין דגל `searchable` מפורש | INV-DM1 | Medium | schema (case_law, chunks) | דגל `searchable` שמסומן רק כשחוזה-השלמות מתקיים |
+| GAP-14 | backlog הלכות סמוי | INV-QA1, G10 | Medium | (אין health-check) | לחשוף `pending_review` ב-health-check / dashboard |
+| GAP-15 | שער-ייצוא נאכף-זרימה ולא נאכף-קוד | INV-QA3, EX3 | Critical | `drafting.py:384` | `export_docx` קורא `validate_decision` + בודק `export_blocked` |
+| GAP-16 | neutral_background קריטי-אך-עובר | INV-QA3 (`05 §1.2`) | High | `qa_validator.py:70` | בלוק-ו ריק/חסר = passed=False; חוסם ייצוא |
+| GAP-17 | active_draft_path נגזר זוחל ל-source-of-truth | INV-EX1, AUD2 | High | `db.py:189` | DOCX = נגזר; re-sync בלוקים אחרי revise/apply_user_edit |
+| GAP-18 | audit_log כמעט לא נכתב | INV-AUD1 | High | `cases.py:203` (היחיד) | כתיבת audit על upload/extract/write_block/export |
+| GAP-19 | אין קישור block→source-chunks | INV-AUD1 | High | `decision_blocks` (model_used בלבד) | לתעד אילו chunks/precedents הזינו כל בלוק |
+| GAP-20 | citation→corpus לא נאכף אוטומטית | INV-AUD3 | Medium | `decision_paragraphs.citations` | ולידציה שכל ציטוט בטקסט פתיר לקורפוס |
+| GAP-21 | cross-company sync ידני ולא-נאכף | INV-MC1 | Medium | `sync_agents_across_companies.py:387-389` | אכיפת `--apply` אחרי שינוי-Master; להרעיש על דילוג adapter_type |
+| GAP-22 | אינטגרציית-Paperclip על נוהל ולא מחסום-קוד | INV-INT1, INT3 | Medium | schema / lint (אין) | אילוץ-schema נגד DB-insert; linter נגד httpx/curl גולמי |
+| GAP-23 | הספ עדיין לא מחובר לסוכנים | INV-AG1 | High | `.claude/agents/HEARTBEAT.md`, agent files | חובת קריאת 00-constitution + ספ-תחום לפני פעולה |
+
+---
+
+## יחידות-תיקון מוצעות (Proposed Fix-Units)
+
+23 הממצאים מקובצים ל-8 יחידות-עבודה קוהרנטיות. הקיבוץ נגזר מהעיקרון שרבים מהממצאים
+נפתרים יחד (כל פערי ה-ingest-asymmetry → יחידה אחת). זהו זרע למשימות TaskMaster
+ולתת-פרויקט 3 (שכבת-שלמות).
+
+### FU-1 — איחוד מסלול-הקליטה (Unify ingest path)
+- **מכסה:** GAP-01, GAP-02, GAP-04, GAP-05
+- **מספק invariants:** INV-ING1, INV-ING3, INV-G2, INV-G4; (תורם ל-DM1/RET2 דרך GAP-02)
+- **effort:** L
+- **תלויות:** — (יסוד — FU-2/FU-3 נשענים עליה)
+- **סוג:** pure-code
+
+### FU-2 — קליטה idempotent + מזהים קנוניים
+- **מכסה:** GAP-03, GAP-06, GAP-07, GAP-08, GAP-13
+- **מספק invariants:** INV-ING2, INV-G3, INV-G1, INV-ID1, INV-ID2, INV-DM2, INV-DM1
+- **effort:** L
+- **תלויות:** FU-1 (מסלול אחד לפני upsert אחיד)
+- **סוג:** **data-migration** — GAP-07 reconciliation של case_number מעורב (chair-confirmed),
+ GAP-08 ניקוי ציטוט-כ-מזהה; + code (upsert key, write-time normalize, דגל searchable)
+
+### FU-3 — re-index באכיפה בשינוי-תוכן
+- **מכסה:** GAP-09
+- **מספק invariants:** INV-DM3, INV-G6, INV-RET (freshness)
+- **effort:** M
+- **תלויות:** FU-1 (re-embed יושב בקליטה הקנונית)
+- **סוג:** **data-migration** — re-chunk/re-embed של רשומות קיימות + טריגר/אכיפה קדימה
+
+### FU-4 — הפרדת-קורפוס נאכפת בכל query
+- **מכסה:** GAP-10, GAP-12
+- **מספק invariants:** INV-RET1, INV-G5
+- **effort:** M
+- **תלויות:** — (עצמאי; דחוף — Critical leak)
+- **סוג:** pure-code
+
+### FU-5 — eval harness + נראות-בריאות
+- **מכסה:** GAP-11, GAP-14
+- **מספק invariants:** INV-RET4, INV-G8, INV-QA1, INV-G10 (נראות backlog)
+- **effort:** M
+- **תלויות:** FU-2 (gold-set יציב דורש מזהים קנוניים)
+- **סוג:** pure-code + **chair-decision** — הגדרת gold-set מתויג דורשת אישור היו"ר
+ (מה "תוצאה נכונה" לכל query)
+
+### FU-6 — שערי-QA נאכפים-קוד (Code-enforced gates)
+- **מכסה:** GAP-15, GAP-16
+- **מספק invariants:** INV-QA3, INV-EX3, INV-G10
+- **effort:** S
+- **תלויות:** — (עצמאי; חוסם עקיפת-ייצוא)
+- **סוג:** pure-code
+
+### FU-7 — Audit-trail + provenance (זרע תת-פרויקט 3)
+- **מכסה:** GAP-17, GAP-18, GAP-19, GAP-20
+- **מספק invariants:** INV-AUD1, INV-AUD2, INV-AUD3, INV-EX1, INV-G9
+- **effort:** L
+- **תלויות:** FU-1 (provenance נלכד בקליטה/כתיבה הקנונית)
+- **סוג:** pure-code (schema-additive) — חלק מ-GAP-17 דורש **data-backfill** קל
+ לסנכרון בלוקים↔DOCX קיימים
+
+### FU-8 — מחסומי-תהליך הופכים למחסומי-קוד
+- **מכסה:** GAP-21, GAP-22, GAP-23
+- **מספק invariants:** INV-MC1, INV-INT1, INV-INT3, INV-AG1
+- **effort:** M
+- **תלויות:** ה-spec גמור (GAP-23 דורש קבצי-ספ יציבים לחבר לסוכנים)
+- **סוג:** pure-code + **chair-decision** — GAP-23 (חיבור ספ לסוכני-Paperclip) הוא
+ prerequisite לתת-פרויקט 5 ומשנה התנהגות-סוכן בייצור
+
+---
+
+## סיכום סיווג לפי סוג-עבודה
+
+- **pure-code (ללא מיגרציה):** FU-1, FU-4, FU-6; הליבה של FU-7, FU-8.
+- **דורש data-migration:** FU-2 (case_number reconciliation, ניקוי ציטוטים), FU-3
+ (re-chunk/re-embed), backfill קל ב-FU-7 (סנכרון בלוקים↔DOCX).
+- **דורש chair-decision:** FU-5 (הגדרת gold-set), FU-8/GAP-23 (חיבור ספ לסוכנים);
+ GAP-07 כבר chair-confirmed (canonical = הצורה הרשמית שהוקצתה).
+
+**רצף מומלץ (תלויות):** FU-1 → FU-2 → FU-3; FU-4 ו-FU-6 במקביל (עצמאיים, Critical);
+FU-7 אחרי FU-1; FU-5 אחרי FU-2; FU-8 אחרי ייצוב-הספ.
diff --git a/docs/superpowers/plans/2026-05-30-system-spec-set.md b/docs/superpowers/plans/2026-05-30-system-spec-set.md
new file mode 100644
index 0000000..60709ec
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-30-system-spec-set.md
@@ -0,0 +1,254 @@
+# System Spec-Set (Sub-Project 1) Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Author the living system spec-set under `docs/spec/` that canonically defines the *עוזר משפטי* system and its invariants ("what is correct"), each invariant backed by ≥3 authoritative sources.
+
+**Architecture:** A `00-constitution.md` keystone (mission, global invariants, engineering rules, invariant template, verification protocol, index) + lifecycle-organized domain files (`01-ingest` … `07-learning`) + cross-cutting files (`X1`…`X5`). Existing docs are cited as verified sources, never duplicated. This is documentation, not code: the "test" is the **verification gate** — every invariant carries ≥3 verified sources or is marked `⚠ UNVERIFIED` and escalated to the chair (never decided solo).
+
+**Tech Stack:** Markdown. Sources verified via WebSearch/WebFetch + primary texts (Nevo for Israeli statutes). Design basis: [docs/superpowers/specs/2026-05-30-system-spec-design.md](../specs/2026-05-30-system-spec-design.md).
+
+**Branch:** `system-spec` (already created; design doc committed at `a5b22da`).
+
+---
+
+## Conventions for every file (apply in each task)
+
+- **Invariant template** (use verbatim structure):
+ ```
+ ### INV-:
+ **כלל:**
+ **מקורות:** <≥3 authorities> | סטטוס: verified / ⚠ UNVERIFIED
+ **אכיפה:**
+ **הפרה ידועה:**
+ ```
+- **Language:** Hebrew prose, English for technical terms and source names (matches project docs + RTL preference).
+- **Length target:** ≤ ~500 lines/file. If exceeding, that domain needs splitting — note it, don't cram.
+- **Citing existing docs:** reference (e.g., `block-schema.md`) as a *source to verify*; if it contradicts the ≥3 authorities, record a one-line audit-finding rather than silently trusting it.
+- **Cross-links:** link sibling spec files by relative path; link global invariants as `00-constitution.md#inv-g`.
+
+## Per-file verification gate (the "test")
+
+A file passes only when ALL hold (this checklist is a literal step in each task):
+1. Every `INV-*` has either ≥3 named authoritative sources (`verified`) or is marked `⚠ UNVERIFIED` with an escalation note.
+2. No placeholder text (`TBD`/`TODO`/"להשלים").
+3. All cross-links resolve to a real file/anchor.
+4. Consistent with `00-constitution.md` (no invariant contradicts a global invariant).
+5. ≤ ~500 lines.
+
+---
+
+## Phase 0 — Scaffold
+
+### Task 0: Create the spec directory
+
+**Files:**
+- Create: `docs/spec/README.md`
+
+- [ ] **Step 1: Create `docs/spec/` with a short README**
+
+Write `docs/spec/README.md`:
+```markdown
+# ספ המערכת — עוזר משפטי (Living System Spec)
+
+זהו מקור-האמת הקנוני ל"מהו תקין" במערכת. שער-הכניסה: [00-constitution.md](00-constitution.md).
+כל invariant מגובה ב-≥3 מקורות סמכותיים; פריט לא-מאומת מסומן ⚠ UNVERIFIED ומועלה ליו"ר.
+
+מבנה: 00 חוקה · 01–07 מחזור-חיים · X1–X5 חוצי-שלבים. ראה אינדקס מלא בחוקה.
+בסיס-עיצוב: docs/superpowers/specs/2026-05-30-system-spec-design.md
+```
+
+- [ ] **Step 2: Commit**
+
+```bash
+git add docs/spec/README.md
+git commit -m "docs(spec): scaffold docs/spec/ living spec-set"
+```
+
+---
+
+## Phase 1 — Keystone (REVIEW CHECKPOINT after)
+
+### Task 1: `00-constitution.md` — the keystone
+
+**Files:**
+- Create: `docs/spec/00-constitution.md`
+
+- [ ] **Step 1: Write the constitution** with these sections (content is already determined by the approved design):
+
+ 1. **ייעוד** — paste the confirmed mission paragraph from the design doc §2.
+ 2. **עקרונות-עבודה** — the 4 work principles (design doc §3): don't assume existing is correct; 3-source protocol; research→draft; collaboration model.
+ 3. **תבנית-invariant** — the template from "Conventions" above.
+ 4. **פרוטוקול-אימות** — `verified` vs `⚠ UNVERIFIED`; escalation to chair; never decide solo.
+ 5. **Invariants גלובליים G1–G11** — each written with the full template. Content + sources from design doc §6 / §9:
+
+ - **INV-G1 מזהה קנוני מנורמל בכתיבה** — SSOT/normalization · Codd 1NF (CACM 13(6), 1970) · Kleppmann DDIA. אכיפה: normalization-on-write in the ingest path + `X1-identifiers.md`. הפרה ידועה: tolerant `_normalize_case_number` on read only; `8126-25` vs `8126-03-25`.
+ - **INV-G2 מקור-אמת יחיד, אין מסלולים מקבילים מתפצלים** — Kleppmann (system of record) · Fowler (Canonical Data Model) · SSOT. אכיפה: one canonical ingest path; siblings share it. הפרה ידועה: `ingest_precedent` vs `ingest_internal_decision` asymmetry.
+ - **INV-G3 ingest אחיד ו-idempotent (upsert על מפתח דטרמיניסטי)** — Kleppmann · Stripe/CDC idempotency · ISO 8000. אכיפה: `01-ingest.md` unified path.
+ - **INV-G4 חוזה-שלמות לפני "שמיש/ניתן-לחיפוש"** — ISO 8000 · DAMA-UK (completeness) · ISO 15489 (reliability). אכיפה: write-validation + health-check; `02-data-model.md`. הפרה ידועה: ערן סופר 8046/24 indexed with empty headnote/summary/tags.
+ - **INV-G5 metadata מלא לכל פריט מואנדקס + הפרדת-קורפוס בכל query** — Pinecone (multitenancy) · RAG attribution (Lewis et al.) · ISO 8000. אכיפה: `03-retrieval.md`. הפרה ידועה: task #56 halacha_filters source_kind leak.
+ - **INV-G6 re-index בכל שינוי תוכן** — Pinecone · Weaviate · RAG freshness. אכיפה: ingest/update path.
+ - **INV-G7 מיזוג RRF לא סכום-ציונים** — Elastic (RRF) · Weaviate · OpenSearch/Azure (corrob.). אכיפה: retrieval fusion (already implemented — codified).
+ - **INV-G8 איכות-אחזור נמדדת (precision+recall)** — Manning IR textbook · RAG eval literature · (Elastic eval guidance). אכיפה: eval harness in `03-retrieval.md`.
+ - **INV-G9 עקיבוּת-מקור + audit-trail ל-AI** — CEPEJ (user control) · NCSC · ISO 15489. אכיפה: `X5-audit-provenance.md`.
+ - **INV-G10 המערכת מסייעת; שערים אנושיים = invariant** — NCSC ("never replace human judgment") · CEPEJ · FJC. אכיפה: `05-qa-review.md` human gates.
+ - **INV-G11 תוכן החלטה מנומקת** (רקע ניטרלי · ללא כפילות · מענה לטענות המפסיד · מבחן-השופט · טענות מקוריות) — FJC Writing Manual · South Bucks [2004] UKHL 33 · חוק לתיקון סדרי המינהל (החלטות והנמקות) תשי"ט-1958. אכיפה: `04-analysis-writing.md` + `05-qa-review.md`.
+
+ 6. **כללי-הנדסה** — סימטריה · נרמול-לא-תיקון-תסמין · quality-at-source (Fowler/Data-Mesh) · אין בליעה שקטה.
+ 7. **אינדקס** — table linking all spec files (00, 01–07, X1–X5) with one-line purpose each.
+ 8. **נספח מקורות** — paste the full source appendix from design doc §9.
+
+- [ ] **Step 2: Run the per-file verification gate** (the 5-point checklist above). Fix inline.
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add docs/spec/00-constitution.md
+git commit -m "docs(spec): 00-constitution — mission, 11 global invariants, engineering rules"
+```
+
+- [ ] **Step 4: REVIEW CHECKPOINT** — present `00-constitution.md` to חיים. Do not start Phase 2 until approved. If the constitution's framing changes, the domain files adapt to it.
+
+---
+
+## Phase 2 — Lifecycle domain files
+
+> Each task: (a) targeted research to verify domain-specific invariants to ≥3 sources (global invariants already verified — reuse their sources; only NEW domain claims need fresh sourcing); (b) draft the file; (c) run the verification gate; (d) commit. Group review checkpoint at end of Phase 2.
+
+### Task 2: `01-ingest.md` — unified intake contract
+
+**Files:** Create `docs/spec/01-ingest.md`
+
+- [ ] **Step 1:** Document the **target single ingest path** for all three intake kinds (case documents / external precedent / internal-committee decisions). Describe the canonical pipeline: stage file → extract text → chunk → embed → store → queue metadata extraction → queue halacha extraction → set statuses. State which steps are **uniform across all kinds** (this is the fix for the asymmetry).
+- [ ] **Step 2:** Define domain invariants applying INV-G2/G3/G4/G6 to ingest, e.g.:
+ - **INV-ING1:** every intake kind flows through the same canonical ingest function; a new kind extends it via parameters, never a parallel function. (sources: INV-G2 set)
+ - **INV-ING2:** ingest is idempotent on the canonical identifier (re-ingest = upsert, no duplicate row/chunks). (sources: INV-G3 set)
+ - **INV-ING3:** metadata extraction is queued for *every* kind that has extractable metadata — not conditional per path. (sources: INV-G4 set; הפרה ידועה: internal path skipped `request_metadata_extraction`)
+- [ ] **Step 3:** Cite current reality as audit-findings (the 8 documented asymmetries from the design research) — as `הפרה ידועה` lines, not as "correct."
+- [ ] **Step 4:** Run verification gate. **Step 5:** Commit `docs(spec): 01-ingest unified intake contract`.
+
+### Task 3: `02-data-model.md` — entities + completeness contract
+
+**Files:** Create `docs/spec/02-data-model.md`
+
+- [ ] **Step 1:** Enumerate the canonical entities (cases, case_law, documents, chunks, halachot, chair_feedback, …) — name, purpose, key fields. Mark this as the **target** model (verify field names against current schema during execution; divergences → audit-findings).
+- [ ] **Step 2:** Define the **completeness contract per entity** — the mandatory-field set that makes a record "usable/searchable" (INV-G4). For `case_law`: e.g., canonical case_number, case_name, court, practice_area, source_kind, + (for searchable) ≥1 chunk and non-empty metadata. State explicitly that records failing the contract are flagged, not silently searchable.
+ - **INV-DM1:** a case_law row is "searchable" only when its completeness contract is satisfied. (sources: ISO 8000 · DAMA-UK · ISO 15489)
+ - **INV-DM2:** each entity has exactly one canonical identifier; no field stores a full citation as the identifier. (sources: INV-G1 set; הפרה ידועה: citation-as-case_number for סופר entries)
+- [ ] **Step 3:** Run gate. **Step 4:** Commit `docs(spec): 02-data-model entities + completeness contract`.
+
+### Task 4: `03-retrieval.md` — corpora + retrieval invariants
+
+**Files:** Create `docs/spec/03-retrieval.md`
+
+- [ ] **Step 1:** Document the 3 corpora + their search tools (source_kind mapping) and the hybrid/RRF design. (Reuse research from design §9 RAG sources — already verified.)
+- [ ] **Step 2:** Define invariants (apply INV-G5/G6/G7/G8/G9):
+ - **INV-RET1:** corpus separation enforced on 100% of query paths (chunks AND halachot filters). (Pinecone · ISO · RAG; הפרה ידועה: task #56)
+ - **INV-RET2:** no item indexed without complete required metadata + resolvable source locator. (INV-G5 set)
+ - **INV-RET3:** heterogeneous retrievers fused by RRF, never raw-score sum. (Elastic · Weaviate)
+ - **INV-RET4:** retrieval quality measured by a standing precision+recall eval harness on a fixed labeled query set. (Manning · RAG eval)
+ - **INV-RET5:** every returned span is attributable to its source. (CEPEJ · RAG)
+- [ ] **Step 3:** Run gate. **Step 4:** Commit `docs(spec): 03-retrieval corpora + retrieval invariants`.
+
+### Task 5: `04-analysis-writing.md` — claims, 12 blocks, Dafna style
+
+**Files:** Create `docs/spec/04-analysis-writing.md`
+
+- [ ] **Step 1:** Reference (cite, don't duplicate) `block-schema.md`, `decision-methodology.md`, `skills/decision/SKILL.md` as sources; summarize the 12-block model + claims extraction at spec altitude.
+- [ ] **Step 2:** Verify the Israeli reasoned-decision sources (design doc §8 open items #1–#3): confirm exact section of חוק 1958 (תשכ"ט-1969 amendment) on Nevo; confirm/locate ברק-ארז citation; confirm בג"ץ 143/56 / עע"ם 2994/21. Mark each `verified` or `⚠ UNVERIFIED` + escalate.
+- [ ] **Step 3:** Define invariants from INV-G11:
+ - **INV-WR1:** block ו (background) is neutral — no judgment words, no party quotes. (FJC · חובת הנמקה)
+ - **INV-WR2:** no duplication — block י references prior blocks, does not restate facts. (FJC §non-duplication)
+ - **INV-WR3:** every losing-side principal argument is addressed. (FJC · South Bucks adequacy)
+ - **INV-WR4:** block ז = original claims only; supplements go to block ח. (project rule; cite corpus-analysis)
+ - **INV-WR5:** judge-unfamiliar-with-case test — decision is self-contained and traceable. (FJC · South Bucks)
+- [ ] **Step 4:** Run gate. **Step 5:** Commit `docs(spec): 04-analysis-writing — 12 blocks + reasoned-decision invariants`.
+
+### Task 6: `05-qa-review.md` — QA gates + human gates
+
+**Files:** Create `docs/spec/05-qa-review.md`
+
+- [ ] **Step 1:** Document the existing automated QA gates (`validate_decision`: neutral_background, claims_coverage, weight_compliance, structural_integrity, no_duplication, sequential_numbering) — as the QA contract (verify against `qa_validator.py` at execution).
+- [ ] **Step 2:** Define human-gate invariants (INV-G10):
+ - **INV-QA1:** halacha approval is a manual chair decision; auto-extracted halachot are `pending_review` until the chair approves. (NCSC · CEPEJ · project rule)
+ - **INV-QA2:** outcome selection and chair feedback are human gates, never automated. (NCSC · CEPEJ · FJC)
+ - **INV-QA3:** a decision cannot be exported while critical QA gates fail. (FJC · validate_decision design)
+- [ ] **Step 3:** Run gate. **Step 4:** Commit `docs(spec): 05-qa-review — QA + human gates`.
+
+### Task 7: `06-export.md` — DOCX export contract
+
+**Files:** Create `docs/spec/06-export.md`
+
+- [ ] **Step 1:** Reference `skills/dafna-decision-template/SKILL.md`; document the export contract: line classification, dash policy, placeholder handling, template styles. Define:
+ - **INV-EX1:** export is deterministic from the stored decision blocks (single source = DB blocks; the DOCX is derived). (INV-G2 derived-data set)
+ - **INV-EX2:** export preserves source traceability where required. (INV-G9)
+- [ ] **Step 2:** Run gate. **Step 3:** Commit `docs(spec): 06-export DOCX contract`.
+
+### Task 8: `07-learning.md` — Hermes, lessons, feedback loop
+
+**Files:** Create `docs/spec/07-learning.md`
+
+- [ ] **Step 1:** Document the learning loop: Hermes curator (post-export analysis), `docs/legal-decision-lessons.md`, chair-feedback weekly analysis. Define:
+ - **INV-LRN1:** curator proposes; changes to SKILL.md/lessons.md require manual chair approval. (INV-G10; project rule)
+ - **INV-LRN2:** quality accountability sits at the source (ingest/authoring), not downstream. (Fowler/Data-Mesh)
+- [ ] **Step 2:** Run gate. **Step 3:** Commit `docs(spec): 07-learning loop`.
+
+- [ ] **Phase 2 REVIEW CHECKPOINT** — present `01`–`07` to חיים for review before Phase 3.
+
+---
+
+## Phase 3 — Cross-cutting files (final REVIEW after)
+
+### Task 9: `X1-identifiers.md` — canonical identifier model
+
+**Files:** Create `docs/spec/X1-identifiers.md`
+
+- [ ] **Step 1:** Define the canonical case_number model: the normalized written form, the relationship `cases.case_number` vs `case_law.case_number`, and citation formats. Specify **normalize-on-write** (INV-G1), with tolerant-match-on-read as a *secondary* convenience, not the primary mechanism.
+ - **INV-ID1:** case_number is normalized to canonical form at write time. (SSOT · Codd · Kleppmann)
+ - **INV-ID2:** no entity uses a full citation string as its identifier. (INV-G1; הפרה ידועה: סופר entries)
+- [ ] **Step 2:** Run gate. **Step 3:** Commit `docs(spec): X1-identifiers canonical model`.
+
+### Task 10: `X2-multi-company.md`
+
+**Files:** Create `docs/spec/X2-multi-company.md`
+
+- [ ] **Step 1:** Document CMP (1xxx) / CMPA (8xxx), 14 agents (7×2), and the sync rules (cite `sync_agents_across_companies.py`, `HEARTBEAT.md`). Define:
+ - **INV-MC1:** any agent-config change in master must be synced to the mirror company via the API sync script. (project rule)
+- [ ] **Step 2:** Run gate. **Step 3:** Commit `docs(spec): X2-multi-company`.
+
+### Task 11: `X3-integration-deploy.md`
+
+**Files:** Create `docs/spec/X3-integration-deploy.md`
+
+- [ ] **Step 1:** Document Paperclip integration (wakeup via API not DB; comment routing via CEO; outbound case-status webhook) and the deploy model (Coolify dockerimage for legal-ai; pm2 for paperclip/chat-service). Define:
+ - **INV-INT1:** Paperclip wakeup goes through `POST /api/agents/{id}/wakeup` with `payload.issueId`, never a direct DB insert. (project rule; cite memory reference)
+ - **INV-INT2:** legal-ai code changes require commit→push→Coolify deploy; no local uvicorn. (project rule)
+- [ ] **Step 2:** Run gate. **Step 3:** Commit `docs(spec): X3-integration-deploy`.
+
+### Task 12: `X4-agents.md`
+
+**Files:** Create `docs/spec/X4-agents.md`
+
+- [ ] **Step 1:** Map the domain agents (ceo, researcher, analyst, writer, qa, proofreader, exporter, hermes) — role + which spec files each must read. Reserve a section for the **process agents** (sub-project 5: add-feature / fix-feature / spec-guardian) to be defined later. Define:
+ - **INV-AG1:** every agent reads `00-constitution.md` first and the relevant domain spec before acting. (governance rule)
+- [ ] **Step 2:** Run gate. **Step 3:** Commit `docs(spec): X4-agents map`.
+
+### Task 13: `X5-audit-provenance.md`
+
+**Files:** Create `docs/spec/X5-audit-provenance.md`
+
+- [ ] **Step 1:** Define the audit-trail + provenance requirements (INV-G9): logging of AI-assisted generation, traceability of every cited authority/source in a decision back to the corpus, record integrity over time.
+ - **INV-AUD1:** every AI-assisted artifact records what sources/data produced it. (CEPEJ user-control · NCSC · ISO 15489)
+ - **INV-AUD2:** record integrity — a stored decision/record is complete and unaltered except via tracked, attributed changes. (ISO 15489 §5.2.2.3)
+- [ ] **Step 2:** Run gate. **Step 3:** Commit `docs(spec): X5-audit-provenance`.
+
+- [ ] **FINAL REVIEW** — present the complete spec-set to חיים. On approval, sub-project 1 is done; proceed to sub-project 2 (Audit) in its own spec→plan cycle.
+
+---
+
+## Self-Review (run after writing this plan)
+
+- **Spec coverage:** every design-doc section maps to a task — mission/principles → Task 1; G1–G11 → Task 1 + applied in 2–13; spec-set structure → Tasks 0–13; verification protocol → conventions + gate; open legal items → Task 5 Step 2. ✓
+- **Placeholder scan:** domain-file invariants are enumerated with IDs + sources, not "define later"; the only deferred content is the process-agents section (Task 12) which is explicitly sub-project 5, and the legal `⚠ UNVERIFIED` items (Task 5) which are an intentional escalation, not a placeholder. ✓
+- **Type/name consistency:** invariant IDs are unique (G1–G11, ING1–3, DM1–2, RET1–5, WR1–5, QA1–3, EX1–2, LRN1–2, ID1–2, MC1, INT1–2, AG1, AUD1–2); file names consistent with design doc §5. ✓
+```
diff --git a/docs/superpowers/specs/2026-05-30-system-spec-design.md b/docs/superpowers/specs/2026-05-30-system-spec-design.md
new file mode 100644
index 0000000..901efa2
--- /dev/null
+++ b/docs/superpowers/specs/2026-05-30-system-spec-design.md
@@ -0,0 +1,168 @@
+# מסמך-עיצוב אב — ספ המערכת והשכבה החסרה (System Spec & Integrity Layer)
+
+**תאריך:** 2026-05-30
+**סטטוס:** עיצוב מאושר (Design approved) — ממתין לכתיבת קבצי הספ
+**בעלים:** חיים מרכוס
+**הקשר:** מהלך-יסוד להגדרת "מהו תקין" במערכת *עוזר משפטי*, ולסגירת כשל-שורש חוזר.
+
+---
+
+## 1. הבעיה — כשל-השורש החוזר
+
+מה שנחווה כ"כל פעם משהו אחר לא מדויק" אינו אוסף תקלות אקראיות אלא **כשל אחד שחוזר בתחפושות**. ראיות שצפו (30.5.2026):
+
+| תסמין | שורש |
+|--------|------|
+| `8126-25` לא נמצא (האמיתי `8126-03-25`); קומיט "tolerant case_number lookup" | אין מפתח קנוני — מתקנים תסמין בקריאה |
+| 3 החלטות "סופר" ב-3 פורמטים שונים (`8126/24`, ציטוט-מלא-כ-case_number) | אין חוזה-נתונים אחיד |
+| ערן סופר 8046/24 עלתה בלי metadata (headnote/summary/tags ריקים) | מסלול ה-ingest הפנימי לא מתזמן חילוץ metadata — אסימטרי למסלול החיצוני |
+| 10/19 הלכות מאושרות, התגלה במקרה | שער ידני שקוף בלי נראות backlog |
+| משימות #56, #57 | אי-עקביות בין רכיבים (דליפה חוצת-קורפוסים, chunker) |
+
+**אבחנה:** המערכת גדלה בקצב *הוספת יכולות* מהר יותר מקצב *שמירת עקביות* — מסלולים/כלים/קורפוסים מקבילים שנוספים בבידוד ומתפצלים (drift), בלי שכבה שמגדירה ואוכפת "תקין". כל פגם מתגלה בדיעבד, אחד-אחד.
+
+**התרופה:** לא לתקן 10 דברים — להוסיף **שכבה אחת חסרה**: חוקה + חוזה-שלמות + בדיקת-בריאות אחת + איחוד מסלולי ה-ingest. זה הופך כשל מ"מתפרץ במקום אקראי" ל"נחסם בכניסה, גלוי בדשבורד".
+
+---
+
+## 2. ייעוד המערכת (מאושר ע"י חיים)
+
+> מערכת AI שמסייעת ליו"ר ועדת הערר לתכנון ובנייה (מחוז ירושלים, עו"ד דפנה תמיר) לנסח **החלטות מעין-שיפוטיות כתובות ומנומקות** — מסמכים משפטיים פורמליים שעומדים לביקורת שיפוטית — תוך שמירה על **הקול, השיקול והאחריות של היו"ר**.
+
+- **משרת:** יו"ר הוועדה (משתמש-על) והסוכנים הפועלים בשמה.
+- **מחזור-חיים:** ניהול תיקים → בסיס ידע (3 קורפוסים) → אחזור סמנטי (RAG) → סיוע-כתיבה (12 בלוקים, סגנון דפנה) → ייצוא DOCX.
+- **3 סוגי עררים:** רישוי ובנייה (1xxx, חם), היטל השבחה (8xxx, קר), פיצויים ס'197 (9xxx, קר).
+- **ה"למה" העמוק:** המערכת מסייעת — היו"ר מכריעה (שערים קריטיים ידניים בכוונה); מנוע צבירת-ידע (לומד מהחלטות סופיות ומפידבק); רב-חברתי (CMP/CMPA).
+
+---
+
+## 3. עקרונות-עבודה למהלך
+
+1. **אסור להניח שהקיים תקין.** כל מה שמופה בקוד/בקורפוס = "טענה לבדיקה", לא "אמת". "תקין" נגזר ממקורות חיצוניים, לא מהמערכת שתחת חשד.
+2. **פרוטוקול אימות 3-מקורות:** כל invariant/חוק בספ מגובה ב-**≥3 מקורות סמכותיים מוכרים** בעלי ידע מקצועי מוכח. כשאין 3 → מסומן `⚠ UNVERIFIED` ומועלה לחיים, לא מוכרע לבד.
+3. **מנגנון:** מחקר עצמאי → טיוטה לביקורת.
+4. **מודל-שיתוף:** על החלטות טכניות/אדריכליות אני חוקר ומכריע מקצועית ומציג תוצאה מוגמרת. שואל את חיים רק במקום שבו *הוא* הסמכות — כוונה, עדיפויות עסקיות, עובדות משפטיות-דומייניות.
+
+---
+
+## 4. פירוק ל-5 תת-פרויקטים (לפי תלות)
+
+| # | תת-פרויקט | תוצר | תלות |
+|---|-----------|------|------|
+| 1 | **ספ המערכת + חוקה** | spec-set ב-`docs/spec/` המגדיר מודל קנוני + invariants | — |
+| 2 | **מפת הפערים (Audit)** | סריקה אמפירית מול הספ → רשימת משימות | תת-פרויקט 1 |
+| 3 | **שכבת שלמות-נתונים** | חוזה-שלמות באכיפת-קוד + בדיקת-בריאות אחת + **איחוד מסלולי ingest** | 1, 2 |
+| 4 | **בדיקה חוזרת** | הרצת בריאות/audit אחרי התיקון | 3 |
+| 5 | **סוכני-תהליך** | add-feature / fix-feature / spec-guardian — מכירים את הספ, "עושים שיעורי בית", לומדים ומתעדכנים | 1 (3) |
+
+כל תת-פרויקט יקבל מחזור spec→plan→implementation משלו. מסמך זה מפרט את **תת-פרויקט 1** במלואו ומקבע את ההחלטות העקרוניות לכולם.
+
+---
+
+## 5. מבנה הספ-set (תת-פרויקט 1)
+
+מיקום: **`docs/spec/`** (ספ חי). ארגון קבצי-תחום: **לפי מחזור-חיים** (גישה A) — חושף ישירות אסימטריות-זרימה.
+
+```
+docs/spec/
+├── 00-constitution.md ← ייעוד · invariants גלובליים · כללי-הנדסה · אינדקס · תבנית-invariant · פרוטוקול-אימות
+│ ── מחזור-החיים ──
+├── 01-ingest.md ← קליטה מאוחדת: מסמכי-תיק / פסיקה חיצונית / החלטות-ועדה — חוזה מסלול-יחיד
+├── 02-data-model.md ← אחסון: ישויות (cases, case_law, documents, chunks, halachot…) + חוזה-שלמות לכל ישות
+├── 03-retrieval.md ← 3 קורפוסים + כלי-חיפוש · hybrid/RRF · attribution · eval harness · invariants
+├── 04-analysis-writing.md ← חילוץ טענות · 12 בלוקים · סגנון דפנה (מצטט block-schema.md וכו')
+├── 05-qa-review.md ← שערי QA + שערים אנושיים (אישור הלכה, בחירת תוצאה, פידבק) כ-invariant
+├── 06-export.md ← ייצוא DOCX לפי תבנית דפנה
+├── 07-learning.md ← Hermes · לקחים · לולאת פידבק היו"ר · צמיחת קורפוס (quality-at-source)
+│ ── חוצי-שלבים ──
+├── X1-identifiers.md ← מודל מזהים קנוני: נרמול case_number **בכתיבה** · cases מול case_law · פורמטי ציטוט
+├── X2-multi-company.md ← CMP/CMPA · 14 סוכנים · כללי sync
+├── X3-integration-deploy.md ← Paperclip (wakeup, ניתוב comments, webhooks) · Coolify/pm2
+├── X4-agents.md ← מפת הסוכנים (דומיין + סוכני-התהליך מתת-פרויקט 5)
+└── X5-audit-provenance.md ← audit-trail לשימוש ב-AI · עקיבוּת כל מקור מצוטט · שלמות-רשומה (CEPEJ/NCSC/ISO 15489)
+```
+
+**עקרונות:** כל קובץ עצמאי, ממוקד, agent-readable, יעד ≤~500 שורות (תפיחה = סימן לפיצול). `00-constitution.md` = שער-כניסה יחיד. מסמכים קיימים (`architecture.md`, `product-specification.md`, `block-schema.md`…) לא נמחקים ולא משוכפלים — מצוטטים כ"מקור" ומאומתים מול הסמכויות; סתירה = ממצא ל-audit.
+
+### תבנית-invariant (מבנה אחיד לכל חוק בספ)
+```
+### INV-<תחום><מספר>: <כותרת קצרה>
+**כלל:** <ניסוח נורמטיבי חד — מה חייב להתקיים>
+**מקורות:** <≥3 סמכויות> | סטטוס: verified / ⚠ UNVERIFIED
+**אכיפה:** <היכן/איך נאכף — schema, ולידציית-כתיבה, בדיקת-בריאות, שער>
+**הפרה ידועה:** <דוגמה מהמערכת, אם יש — מקשר ל-audit>
+```
+
+---
+
+## 6. ה-Invariants הגלובליים (לב `00-constitution.md`)
+
+כל אחד מגובה ב-≥3 סמכויות (פירוט ב-§9). אלה החוקים שמייבשים את כשל-השורש:
+
+| # | Invariant | סמכויות |
+|---|-----------|---------|
+| **G1** | מזהה קנוני, **מנורמל בכתיבה** (לא תיקון-סלחני בקריאה בלבד) | SSOT/normalization · Codd 1NF · Kleppmann |
+| **G2** | מקור-אמת יחיד; **אין מסלולי-קוד מקבילים שמתפצלים** — אחים חולקים מסלול קנוני אחד; derived data משוחזר | Kleppmann (system of record) · Fowler (canonical model) · SSOT |
+| **G3** | ingest **אחיד ו-idempotent** (upsert על מפתח דטרמיניסטי) | Kleppmann · Stripe/CDC idempotency · ISO 8000 |
+| **G4** | **חוזה-שלמות:** שדות חובה מולאו לפני שרשומה "שמישה/ניתנת-לחיפוש"; נבדק מול spec מפורש | ISO 8000 · DAMA (completeness) · ISO 15489 (reliability) |
+| **G5** | metadata מלא לכל פריט מואנדקס + **הפרדת-קורפוס נאכפת בכל מסלול-query** | Pinecone (multitenancy) · RAG attribution · ISO 8000 |
+| **G6** | **re-index בכל שינוי תוכן** (אין embeddings מיושנים) | Pinecone · Weaviate · RAG freshness |
+| **G7** | מיזוג **לפי דירוג (RRF)**, לא סכום-ציונים גולמי בין retrievers | Elastic · Weaviate · OpenSearch/Azure (corrob.) |
+| **G8** | איכות-אחזור **נמדדת (precision+recall)**, לא מונחת | Manning (IR textbook) · RAG eval literature |
+| **G9** | כל פלט **עקיב למקורו** + audit-trail לשימוש ב-AI | CEPEJ (user control) · NCSC · ISO 15489 |
+| **G10** | המערכת מסייעת; **שערים אנושיים** (אישור הלכה/תוצאה/פידבק) הם invariant, לא רשות | NCSC · CEPEJ · FJC |
+| **G11** | **תוכן החלטה מנומקת:** רקע ניטרלי · ללא כפילות · מענה לטענות המפסיד · מבחן-השופט · טענות מקוריות | FJC (Writing Manual) · South Bucks (adequacy) · חוק 1958 (חובת הנמקה) |
+
+### כללי-הנדסה (constitution — מונעים הישנות)
+- **סימטריה:** אסור להוסיף מסלול מקביל ליכולת קיימת — מרחיבים את המסלול הקנוני. (נגזר מ-G2)
+- **נרמול לא תיקון-תסמין:** מתקנים נתון במקור (קנוני), לא מטליאים בקריאה. (נגזר מ-G1)
+- **Quality-at-source:** שלמות נאכפת קרוב ככל האפשר לקליטה. (Fowler/Data-Mesh)
+- **אין בליעה שקטה:** רשומה חסרה מסומנת ומדווחת, לא מתקבלת בשקט. (תואם feedback קיים)
+
+---
+
+## 7. פרוטוקול-אימות ומודל-שיתוף (ייכנס ל-`00-constitution.md`)
+
+- כל invariant נושא `מקורות` + `סטטוס: verified / ⚠ UNVERIFIED`.
+- `⚠ UNVERIFIED` (פחות מ-3 מקורות) → לא מוכרע לבד; מועלה לחיים.
+- החלטות טכניות → מחקר עצמאי + הכרעה מקצועית + הצגת תוצאה. שאלה לחיים רק במקום שהוא הסמכות.
+
+---
+
+## 8. פריטים פתוחים — אימות-מקור-ראשוני נדרש
+(החוקר אימת מסגרת; הפריטים הישראליים דורשים אימות לפני ציטוט כ-סמכות, בשלב כתיבת `04`/`05`/`X5`)
+1. מספר הסעיף המדויק בחוק לתיקון סדרי המינהל (החלטות והנמקות) תשי"ט-1958 (וכן תיקון תשכ"ט-1969).
+2. ציטוט מדויק מ-ברק-ארז, *משפט מינהלי*.
+3. אסמכתאות פסיקה: בג"ץ 143/56; עע"ם 2994/21 (מעמד ועדת ערר כגוף תכנוני-מקצועי).
+
+---
+
+## 9. נספח מקורות סמכותיים (מאומתים במחקר 30.5.2026)
+
+**ממשל-AI שיפוטי + מבנה החלטה מנומקת**
+- NCSC / JTC — *Court Technology Standards* + *Principles & Practices for AI Use in Courts*. https://www.ncsc.org/our-centers-projects/joint-technology-committee/court-technology-standards
+- Federal Judicial Center — *Judicial Writing Manual* (2d ed.). https://www.fjc.gov/content/judicial-writing-manual-pocket-guide-judges-second-edition
+- Council of Europe / CEPEJ — *European Ethical Charter on the use of AI in judicial systems* (2018).
+- *South Buckinghamshire DC v Porter (No 2)* [2004] UKHL 33 (adequacy of reasons). https://publications.parliament.uk/pa/ld200304/ldjudgmt/jd040701/south-1.htm
+- חוק לתיקון סדרי המינהל (החלטות והנמקות), תשי"ט-1958. https://www.nevo.co.il/law_html/law00/98603.htm
+- Kevin D. Ashley — *Artificial Intelligence and Legal Analytics* (CUP).
+
+**אחזור / RAG / IR**
+- Lewis et al. (2020) — *Retrieval-Augmented Generation* (NeurIPS). https://arxiv.org/abs/2005.11401
+- Manning, Raghavan & Schütze — *Introduction to Information Retrieval* (CUP, 2008). https://nlp.stanford.edu/IR-book/
+- Elastic — *Reciprocal Rank Fusion*. https://www.elastic.co/docs/reference/elasticsearch/rest-apis/reciprocal-rank-fusion
+- Pinecone — *Implement multitenancy*. https://docs.pinecone.io/guides/index-data/implement-multitenancy
+- Weaviate — *Hybrid Search Explained*. https://weaviate.io/blog/hybrid-search-explained
+
+**שלמות-נתונים / איכות / רשומות**
+- DAMA-DMBOK2 + DAMA-UK — *Six Primary Dimensions for Data Quality* (2013).
+- ISO 8000 — Data quality (8000-8/61/110).
+- ISO 15489-1:2016 — Records management (authenticity/reliability/integrity/usability).
+- Martin Kleppmann — *Designing Data-Intensive Applications* (O'Reilly, 2017).
+- E.F. Codd — Relational model & normalization (CACM 13(6), 1970).
+- Martin Fowler — Canonical Data Model / Data Mesh (quality-at-source).
+
+---
+
+## 10. השלב הבא
+לאחר ביקורת חיים על מסמך זה → invoke `writing-plans` לבניית תוכנית-יישום מפורטת לתת-פרויקט 1 (כתיבת קבצי הספ-set, החל מ-`00-constitution.md`).