Compare commits
29 Commits
fix/fu4-co
...
system-spe
| Author | SHA1 | Date | |
|---|---|---|---|
| d72d5429ed | |||
| 28bed4906c | |||
| ebfda74575 | |||
| e3880aef4e | |||
| 380998da17 | |||
| 8c4b8cf19e | |||
| b0351958db | |||
| c881665b7c | |||
| 7fd6d8cb95 | |||
| 951f2366e6 | |||
| a0004f0274 | |||
| f0fd405f4e | |||
| b0e4e14832 | |||
| b46d25f605 | |||
| 0fd06659da | |||
| c0ef90d722 | |||
| c1872aa214 | |||
| 1582556b0b | |||
| 5e80bf560d | |||
| 72737df154 | |||
| 998194462f | |||
| 9199214b7c | |||
| da80bcf0fe | |||
| 6afd155dc1 | |||
| 1daaa4861b | |||
| fd682d130f | |||
| c351d6d714 | |||
| 1d01135e32 | |||
| a5b22dadf3 |
@@ -2000,6 +2000,348 @@
|
|||||||
"testStrategy": "document_list על 7 וריאציות פורמט של תיק קיים → כולן מחזירות את אותם מסמכים; תיק לא-קיים אמיתי עדיין מחזיר 'לא נמצא'.",
|
"testStrategy": "document_list על 7 וריאציות פורמט של תיק קיים → כולן מחזירות את אותם מסמכים; תיק לא-קיים אמיתי עדיין מחזיר 'לא נמצא'.",
|
||||||
"subtasks": [],
|
"subtasks": [],
|
||||||
"updatedAt": "2026-05-30T11:54:34.291Z"
|
"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": {
|
"metadata": {
|
||||||
|
|||||||
276
docs/spec/00-constitution.md
Normal file
276
docs/spec/00-constitution.md
Normal file
@@ -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.)
|
||||||
150
docs/spec/01-ingest.md
Normal file
150
docs/spec/01-ingest.md
Normal file
@@ -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) — שלמות-רשומה + עקיבוּת-מקור של פריט נקלט.
|
||||||
155
docs/spec/02-data-model.md
Normal file
155
docs/spec/02-data-model.md
Normal file
@@ -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) — שלמות-רשומה + עקיבוּת-מקור.
|
||||||
178
docs/spec/03-retrieval.md
Normal file
178
docs/spec/03-retrieval.md
Normal file
@@ -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).
|
||||||
186
docs/spec/04-analysis-writing.md
Normal file
186
docs/spec/04-analysis-writing.md
Normal file
@@ -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).
|
||||||
198
docs/spec/05-qa-review.md
Normal file
198
docs/spec/05-qa-review.md
Normal file
@@ -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 ועקיבוּת-מקור.
|
||||||
168
docs/spec/06-export.md
Normal file
168
docs/spec/06-export.md
Normal file
@@ -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 מסומן `<w:rtl/>` (`_mark_run_rtl`) — אחרת Word נופל ל-Times New Roman
|
||||||
|
במקום David. ראה [`references/rtl-runs.md`](../../skills/dafna-decision-template/references/rtl-runs.md).
|
||||||
|
- **מספור:** מספור אוטומטי רק ב-`List Paragraph` (decimal); שורות `(א)(ב)` מקבלות
|
||||||
|
`List Paragraph` עם `_strip_numpr()` (המספור העברי בטקסט).
|
||||||
|
|
||||||
|
## 3. רישום הגרסה — `active_draft_path` + git
|
||||||
|
|
||||||
|
לאחר כתיבת ה-DOCX, `export_docx` (`drafting.py:404-408`):
|
||||||
|
|
||||||
|
1. **`set_active_draft_path(case_id, path)`** (`db.py:1177`) — רושם את ה-DOCX שיוצא כ-
|
||||||
|
active-draft הנוכחי (`cases.active_draft_path`, `db.py:189`). שדה זה הוא **האנקור לעריכות
|
||||||
|
עוקבות** (`revise_draft`/`apply_user_edit`/`list_bookmarks`), לא מקור-אמת-תוכן מתחרה ל-DB.
|
||||||
|
2. **`git_sync.commit_and_push(case_dir, "ייצוא DOCX: …")`** (`drafting.py:408`) — מקבע את
|
||||||
|
הקובץ ב-git של תיקיית-התיק (audit-trail של פלט,
|
||||||
|
[INV-G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai); ראה
|
||||||
|
[X5-audit-provenance.md](X5-audit-provenance.md)).
|
||||||
|
|
||||||
|
אותו דפוס (`set_active_draft_path` + commit) חוזר ב-`export_interim_draft` (`drafting.py:533,536`),
|
||||||
|
`revise_draft` (`drafting.py:692,695`) ו-`apply_user_edit` (`drafting.py:579,582`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Invariants של התחום
|
||||||
|
|
||||||
|
### INV-EX1: ייצוא דטרמיניסטי ומשוחזר מהבלוקים — DOCX הוא נתון-נגזר (→G2)
|
||||||
|
**כלל:** הייצוא **דטרמיניסטי וניתן-לשחזור** מבלוקי-ההחלטה המאוחסנים ב-`decision_blocks`:
|
||||||
|
אותם בלוקים + אותו טמפלט מפיקים מסמך שקול. ה-DOCX הוא **נתון-נגזר (derived)** — **לעולם לא
|
||||||
|
מקור-אמת עצמאי**. אסור מסלול-תוכן שני שכותב DOCX ממקור שאינו ה-DB; וריאציות (final/interim)
|
||||||
|
הן תצוגות של אותו מקור.
|
||||||
|
**מקורות:** Martin Kleppmann — *Designing Data-Intensive Applications* (O'Reilly, 2017,
|
||||||
|
system-of-record מול derived data, ושחזור derived מהמקור) · Martin Fowler (Canonical Data
|
||||||
|
Model / Single Source of Truth) · SSOT (Single Source of Truth principle) | סטטוס: verified
|
||||||
|
**אכיפה:** `export_decision` שולף אך-ורק מ-`decision_blocks` (`docx_exporter.py:336-342`);
|
||||||
|
פלט מגורסן + idempotent מבחינת-תוכן; אוכף את
|
||||||
|
[INV-G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) וכלל-ההנדסה
|
||||||
|
"סימטריה" (חוקה §6).
|
||||||
|
**הפרה ידועה:** אחרי `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).
|
||||||
189
docs/spec/07-learning.md
Normal file
189
docs/spec/07-learning.md
Normal file
@@ -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).
|
||||||
7
docs/spec/README.md
Normal file
7
docs/spec/README.md
Normal file
@@ -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
|
||||||
168
docs/spec/X1-identifiers.md
Normal file
168
docs/spec/X1-identifiers.md
Normal file
@@ -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) — עקיבוּת-מקור (הציטוט כשדה-תצוגה נגזר).
|
||||||
157
docs/spec/X2-multi-company.md
Normal file
157
docs/spec/X2-multi-company.md
Normal file
@@ -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 לפי חברה).
|
||||||
212
docs/spec/X3-integration-deploy.md
Normal file
212
docs/spec/X3-integration-deploy.md
Normal file
@@ -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 <METHOD> <PATH> [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.
|
||||||
140
docs/spec/X4-agents.md
Normal file
140
docs/spec/X4-agents.md
Normal file
@@ -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 (סינון-חברה, זרימה).
|
||||||
163
docs/spec/X5-audit-provenance.md
Normal file
163
docs/spec/X5-audit-provenance.md
Normal file
@@ -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) — קליטה שמייצרת את הקטעים שאליהם פותרים ציטוטים.
|
||||||
125
docs/spec/gap-audit.md
Normal file
125
docs/spec/gap-audit.md
Normal file
@@ -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 אחרי ייצוב-הספ.
|
||||||
254
docs/superpowers/plans/2026-05-30-system-spec-set.md
Normal file
254
docs/superpowers/plans/2026-05-30-system-spec-set.md
Normal file
@@ -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-<DOMAIN><n>: <short title>
|
||||||
|
**כלל:** <one crisp normative statement — what MUST hold>
|
||||||
|
**מקורות:** <≥3 authorities> | סטטוס: verified / ⚠ UNVERIFIED
|
||||||
|
**אכיפה:** <where/how enforced — schema / write-validation / health-check / human gate>
|
||||||
|
**הפרה ידועה:** <example from the system if any → links to audit; else "—">
|
||||||
|
```
|
||||||
|
- **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<n>`.
|
||||||
|
|
||||||
|
## 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. ✓
|
||||||
|
```
|
||||||
168
docs/superpowers/specs/2026-05-30-system-spec-design.md
Normal file
168
docs/superpowers/specs/2026-05-30-system-spec-design.md
Normal file
@@ -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`).
|
||||||
@@ -3165,10 +3165,7 @@ async def search_precedent_library_semantic(
|
|||||||
of halacha review status.
|
of halacha review status.
|
||||||
"""
|
"""
|
||||||
pool = await get_pool()
|
pool = await get_pool()
|
||||||
halacha_filters = [
|
halacha_filters = ["h.review_status IN ('approved', 'published')"]
|
||||||
"h.review_status IN ('approved', 'published')",
|
|
||||||
f"cl.source_kind = '{source_kind}'",
|
|
||||||
]
|
|
||||||
chunk_filters = [f"cl.source_kind = '{source_kind}'"]
|
chunk_filters = [f"cl.source_kind = '{source_kind}'"]
|
||||||
h_params: list = [query_embedding, limit]
|
h_params: list = [query_embedding, limit]
|
||||||
c_params: list = [query_embedding, limit]
|
c_params: list = [query_embedding, limit]
|
||||||
@@ -3401,10 +3398,7 @@ async def search_precedent_library_lexical(
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
pool = await get_pool()
|
pool = await get_pool()
|
||||||
halacha_filters = [
|
halacha_filters = ["h.review_status IN ('approved', 'published')"]
|
||||||
"h.review_status IN ('approved', 'published')",
|
|
||||||
f"cl.source_kind = '{source_kind}'",
|
|
||||||
]
|
|
||||||
chunk_filters = [f"cl.source_kind = '{source_kind}'"]
|
chunk_filters = [f"cl.source_kind = '{source_kind}'"]
|
||||||
# $1 = query, $2 = limit. Filters append starting at $3.
|
# $1 = query, $2 = limit. Filters append starting at $3.
|
||||||
h_params: list = [query, limit]
|
h_params: list = [query, limit]
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
"""Regression test for GAP-10 / INV-RET1: corpus separation enforced on
|
|
||||||
EVERY precedent-library query path — including the halacha sub-query.
|
|
||||||
|
|
||||||
Bug: ``search_precedent_library_semantic`` and
|
|
||||||
``search_precedent_library_lexical`` filtered the *chunk* sub-query by
|
|
||||||
``cl.source_kind`` but NOT the *halacha* sub-query. So an external
|
|
||||||
(``source_kind='external_upload'``) search leaked internal-committee
|
|
||||||
halachot, and an internal search leaked external-ruling halachot — a
|
|
||||||
cross-corpus contamination of the rule-level results.
|
|
||||||
|
|
||||||
Fix: the same ``cl.source_kind = '<kind>'`` predicate that gates the
|
|
||||||
chunk query now also gates the halacha query, in BOTH functions.
|
|
||||||
|
|
||||||
This test runs fully OFFLINE — it monkeypatches ``db.get_pool`` with a
|
|
||||||
fake pool that captures every SQL string passed to ``fetch`` instead of
|
|
||||||
hitting Postgres. It asserts the captured halacha SQL carries the
|
|
||||||
source_kind predicate identical to the chunk SQL.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from legal_mcp.services import db
|
|
||||||
|
|
||||||
|
|
||||||
class _FakePool:
|
|
||||||
"""Captures SQL passed to ``fetch``; returns no rows."""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
|
||||||
self.queries: list[str] = []
|
|
||||||
|
|
||||||
async def fetch(self, sql: str, *args) -> list: # noqa: ANN002
|
|
||||||
self.queries.append(sql)
|
|
||||||
return []
|
|
||||||
|
|
||||||
|
|
||||||
def _classify(queries: list[str]) -> tuple[str, str]:
|
|
||||||
"""Return (halacha_sql, chunk_sql) from the captured queries."""
|
|
||||||
halacha = next(q for q in queries if "FROM halachot h" in q)
|
|
||||||
chunk = next(q for q in queries if "FROM precedent_chunks pc" in q)
|
|
||||||
return halacha, chunk
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def fake_pool(monkeypatch: pytest.MonkeyPatch) -> _FakePool:
|
|
||||||
pool = _FakePool()
|
|
||||||
|
|
||||||
async def _get_pool() -> _FakePool:
|
|
||||||
return pool
|
|
||||||
|
|
||||||
monkeypatch.setattr(db, "get_pool", _get_pool)
|
|
||||||
return pool
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("source_kind", ["external_upload", "internal_committee"])
|
|
||||||
def test_semantic_halacha_query_is_source_kind_scoped(
|
|
||||||
fake_pool: _FakePool, source_kind: str
|
|
||||||
) -> None:
|
|
||||||
asyncio.run(
|
|
||||||
db.search_precedent_library_semantic(
|
|
||||||
query_embedding=[0.0] * 8,
|
|
||||||
source_kind=source_kind,
|
|
||||||
include_halachot=True,
|
|
||||||
limit=5,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
halacha_sql, chunk_sql = _classify(fake_pool.queries)
|
|
||||||
predicate = f"cl.source_kind = '{source_kind}'"
|
|
||||||
assert predicate in chunk_sql, "chunk query must be source_kind-scoped (precondition)"
|
|
||||||
assert predicate in halacha_sql, (
|
|
||||||
"halacha query MUST carry the same source_kind predicate as the "
|
|
||||||
"chunk query — otherwise cross-corpus halacha leakage (GAP-10)"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("source_kind", ["external_upload", "internal_committee"])
|
|
||||||
def test_lexical_halacha_query_is_source_kind_scoped(
|
|
||||||
fake_pool: _FakePool, source_kind: str
|
|
||||||
) -> None:
|
|
||||||
asyncio.run(
|
|
||||||
db.search_precedent_library_lexical(
|
|
||||||
query="zoning setback",
|
|
||||||
source_kind=source_kind,
|
|
||||||
include_halachot=True,
|
|
||||||
limit=5,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
halacha_sql, chunk_sql = _classify(fake_pool.queries)
|
|
||||||
predicate = f"cl.source_kind = '{source_kind}'"
|
|
||||||
assert predicate in chunk_sql, "chunk query must be source_kind-scoped (precondition)"
|
|
||||||
assert predicate in halacha_sql, (
|
|
||||||
"halacha query MUST carry the same source_kind predicate as the "
|
|
||||||
"chunk query — otherwise cross-corpus halacha leakage (GAP-10)"
|
|
||||||
)
|
|
||||||
Reference in New Issue
Block a user