From 37c56ff22ab426f9754179490562ec8b7bb9b78e Mon Sep 17 00:00:00 2001 From: Chaim Date: Sun, 31 May 2026 16:21:27 +0000 Subject: [PATCH] =?UTF-8?q?docs(spec):=20cycle-2=20=E2=80=94=208=20applica?= =?UTF-8?q?tion-surface=20domains=20(X6=E2=80=93X10)=20+=20ui-audit=20+=20?= =?UTF-8?q?GAP-24..62/FU-9..15?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the system spec beyond the core pipeline to the 8 surfaces outside it: - X6 UI↔API contract + design rules (INV-UI1..6) - X7 Paperclip client & connection params (INV-INT4..8) - X8 field-population & extraction provenance (INV-FP1..5) - X9 MCP tool contract — 71 tools (INV-TOOL1..6) - X10 deploy/env/secrets (INV-ENV1..5) - ui-audit.md — page-by-page UI audit (13 pages) - 02-data-model: derived-entity invariants (INV-DM4..6) - X4-agents: tool-grant map + INV-AG3 - gap-audit: GAP-24..62 → FU-9..15; cycle-1 (FU-1..8b) marked done - constitution §7 + README index (X1..X10) Planning/spec artifacts only — no application code. All engineering invariants backed by ≥3 sources; every finding carries verified file:line. Co-Authored-By: Claude Opus 4.8 --- docs/spec/00-constitution.md | 11 +- docs/spec/02-data-model.md | 37 ++++++ docs/spec/README.md | 6 +- docs/spec/X10-deploy-env-secrets.md | 86 +++++++++++++ docs/spec/X3-integration-deploy.md | 8 ++ docs/spec/X4-agents.md | 23 ++++ docs/spec/X6-ui-api-contract.md | 108 +++++++++++++++++ docs/spec/X7-paperclip-client-params.md | 155 ++++++++++++++++++++++++ docs/spec/X8-field-provenance.md | 118 ++++++++++++++++++ docs/spec/X9-mcp-tool-contract.md | 101 +++++++++++++++ docs/spec/gap-audit.md | 97 ++++++++++++++- docs/spec/ui-audit.md | 72 +++++++++++ 12 files changed, 819 insertions(+), 3 deletions(-) create mode 100644 docs/spec/X10-deploy-env-secrets.md create mode 100644 docs/spec/X6-ui-api-contract.md create mode 100644 docs/spec/X7-paperclip-client-params.md create mode 100644 docs/spec/X8-field-provenance.md create mode 100644 docs/spec/X9-mcp-tool-contract.md create mode 100644 docs/spec/ui-audit.md diff --git a/docs/spec/00-constitution.md b/docs/spec/00-constitution.md index 98d72d5..b9b8056 100644 --- a/docs/spec/00-constitution.md +++ b/docs/spec/00-constitution.md @@ -216,7 +216,7 @@ Manual* (2d ed.) | סטטוס: verified ## 7. אינדקס הספ -> הערה: כל קבצי הספ (00, 01–07, X1–X5) קיימים. החוקה היא שער-הכניסה; כל קובץ-תחום כפוף לה. +> הערה: כל קבצי הספ (00, 01–07, X1–X10) קיימים. החוקה היא שער-הכניסה; כל קובץ-תחום כפוף לה. | קובץ | תפקיד | אוכף invariants | |------|--------|-----------------| @@ -233,6 +233,15 @@ Manual* (2d ed.) | סטטוס: verified | [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 | +| [X6-ui-api-contract.md](X6-ui-api-contract.md) | web-ui ↔ API: OpenAPI=SSoT · response models · envelope · SSE · חוזי-טופס + כללי-עיצוב | G2, G4, G9 (UI) | +| [X7-paperclip-client-params.md](X7-paperclip-client-params.md) | לקוח-Paperclip קנוני · IDs/env/keys מ-config · webhook idempotency/אירוע מגורס | G2, G9 (תפעולי) | +| [X8-field-provenance.md](X8-field-provenance.md) | מקור-מילוי כל שדה (דטרמיניסטי/Opus/ידני/נגזר) · preservation · trust · verbatim-quote | G9, G10 | +| [X9-mcp-tool-contract.md](X9-mcp-tool-contract.md) | חוזה 71 כלי-ה-MCP: envelope · שמות · idempotency · extract/get-symmetry · שלמות-הרשאות | G2, G3, G10 | +| [X10-deploy-env-secrets.md](X10-deploy-env-secrets.md) | env-catalog SSoT · מקור-config יחיד (Coolify) · ללא hardcode · secrets · drift | G2, G4, G9 | + +> **X6–X10 (מחזור-2):** מכסים את 8 משטחי-האפליקציה שמחוץ לצינור-הליבה (אינטגרציה, web-ui, מילוי-שדות, +> אחסון-ניתוחים, כלי-MCP, deploy/env). הממצאים ב-[gap-audit.md](gap-audit.md) (GAP-24..62 → FU-9..15) +> וב-[ui-audit.md](ui-audit.md). הרחבות-אחות: [02-data-model](02-data-model.md) (INV-DM4–DM6), [X4-agents](X4-agents.md) (INV-AG3). **עקרונות:** כל קובץ עצמאי, ממוקד, agent-readable, יעד ≤~500 שורות (תפיחה = סימן לפיצול). מסמכים קיימים (`architecture.md`, `product-specification.md`, `block-schema.md`…) diff --git a/docs/spec/02-data-model.md b/docs/spec/02-data-model.md index c8c3e9e..427ef40 100644 --- a/docs/spec/02-data-model.md +++ b/docs/spec/02-data-model.md @@ -76,6 +76,19 @@ proceeding_type)`. לכן המזהה הקנוני הוא **(`case_number` מנו - `decision_blocks` → usable: `block_id`∈12-הבלוקים; "מוכן": `status=final` ו-`content` לא-ריק. - `chair_feedback` → usable: `feedback_text`+`category` מהמילון; "פתוח" עד `resolved=true`. +### 2ג. ישויות-נגזרות (אחסון-ניתוחים) + +מעבר לישויות-המקור, המערכת **שומרת ניתוחים נגזרים** — תוצרי-חילוץ של LLM/קוד. אלו כפופים לכללי +ה-provenance של [X8](X8-field-provenance.md) ולשערי [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant): + +| ישות-נגזרת | מקור-מילוי | שער-אישור | קישור-מקור | +|------------|------------|-----------|------------| +| `claims` | OPUS (`extract_claims`) | — | `source_document` (string, לא-FK) | +| `legal_arguments` (+`legal_argument_propositions`) | OPUS (`aggregate_claims_to_arguments`) | **חסר** (בניגוד ל-halachot) | `cited_precedents TEXT[]` (לא-FK) | +| `appraiser_facts` | OPUS (`extract_appraiser_facts`) | — | `document_id` (FK); `appraiser_side` default `''` | +| `halachot` | OPUS (`halacha_extractor`) | **`review_status`** ✓ | `case_law_id` (FK); `quote_verified` | +| `decision_blocks` / `decision_paragraphs` | Opus/script (`write_block`) | `status` | `model_used` + audit-event provenance (FU-7); `citations JSONB` ללא-FK | + --- ## 3. Invariants של התחום @@ -120,6 +133,28 @@ RAG freshness (Lewis et al., 2020, NeurIPS) | סטטוס: verified [G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן). **הפרה ידועה:** — +### INV-DM4: לכל ישות-נגזרת — provenance מוצהר +**כלל:** כל ישות-נגזרת (claims, legal_arguments, appraiser_facts, decision_blocks, halachot) נושאת +**provenance** — מי/מה הפיק (מודל, גרסה, זמן) ולאילו chunks/מקורות היא קשורה. מופע של +[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai); מקביל ל-[X8 INV-FP1](X8-field-provenance.md). +**מקורות:** ISO 8000-110 (data lineage) · DAMA-DMBOK2 (lineage) · ISO 15489-1:2016 (records authenticity) | סטטוס: verified +**אכיפה:** עמודות-provenance + קישור block→source (חלקית דרך audit-event ב-FU-7/GAP-19; ל-legal_arguments טרם). +**הפרה ידועה:** `legal_arguments` ללא provenance; `embedding` ללא model/version ([gap-audit GAP-42](gap-audit.md)). + +### INV-DM5: פלט-ניתוח של LLM נכנס בשער-אישור (כמו halachot) +**כלל:** ישות-נגזרת שמוּלאת ע"י LLM ומשפיעה על ההחלטה נכנסת **לא-מאושרת** עד אישור-יו"ר — אותו שער כמו +`halachot.review_status`. מופע של [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant); תואם [X8 INV-FP3](X8-field-provenance.md). +**מקור-סמכות:** דפוס `halachot.review_status` (`db.py:659`); [05-qa-review.md](05-qa-review.md). (פרויקטלי-תפעולי — משרת G10.) +**אכיפה:** שדה-סטטוס-אישור על ישויות-נגזרות מהותיות. +**הפרה ידועה:** `legal_arguments` **חסר** שער-אישור — נכתב ומשמש ללא בקרת-יו"ר ([gap-audit GAP-39](gap-audit.md)). + +### INV-DM6: ולידציה — CHECK-enums, FK לציטוטים, ללא טבלאות-מקבילות +**כלל:** ערכי-enum נאכפים ב-CHECK (לא TEXT חופשי); ציטוט-מקור נשמר כ-FK (לא string/array חופשי); אין שתי +טבלאות לאותה ישות. מופע של [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש) ו-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). **הנדסי.** +**מקורות:** E.F. Codd (referential integrity, CACM 1970) · ISO 8000 (validity) · Kleppmann *DDIA* | סטטוס: verified +**אכיפה:** CHECK על enums; FK על `cited_precedents`/`decision_paragraphs.citations`; איחוד `case_precedents`↔`case_law`. +**הפרה ידועה:** 20+ enums כ-TEXT חופשי; `legal_arguments.cited_precedents TEXT[]` ללא-FK (הזיות-LLM נבלעות); `case_precedents` מול `case_law` מקבילות ([gap-audit GAP-40/42/43](gap-audit.md)). + --- ## 4. מצב קיים מול יעד — audit-findings @@ -153,3 +188,5 @@ RAG freshness (Lewis et al., 2020, NeurIPS) | סטטוס: verified - [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) — שלמות-רשומה + עקיבוּת-מקור. +- [X8-field-provenance.md](X8-field-provenance.md) — מקור-מילוי השדות (בסיס ל-INV-DM4/DM5). +- [X9-mcp-tool-contract.md](X9-mcp-tool-contract.md) — הכלים שמייצרים את הישויות-הנגזרות. diff --git a/docs/spec/README.md b/docs/spec/README.md index 60226fd..9eab12f 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -3,5 +3,9 @@ זהו מקור-האמת הקנוני ל"מהו תקין" במערכת. שער-הכניסה: [00-constitution.md](00-constitution.md). כל invariant מגובה ב-≥3 מקורות סמכותיים; פריט לא-מאומת מסומן ⚠ UNVERIFIED ומועלה ליו"ר. -מבנה: 00 חוקה · 01–07 מחזור-חיים · X1–X5 חוצי-שלבים. ראה אינדקס מלא בחוקה. +מבנה: 00 חוקה · 01–07 מחזור-חיים · X1–X10 חוצי-שלבים. ראה אינדקס מלא בחוקה. +- X1–X5: מזהים · רב-חברתי · אינטגרציה+deploy · סוכנים · audit. +- X6–X10 (מחזור-2, 8 משטחי-האפליקציה): חוזה UI↔API · לקוח-Paperclip · מילוי-שדות · חוזה כלי-MCP · deploy/env/secrets. + +מפות-ממצאים: [gap-audit.md](gap-audit.md) (GAP-01..62 → FU-1..15; מחזור-1 ✅ הושלם, מחזור-2 פתוח) · [ui-audit.md](ui-audit.md) (ביקורת 13 דפי-UI). בסיס-עיצוב: docs/superpowers/specs/2026-05-30-system-spec-design.md diff --git a/docs/spec/X10-deploy-env-secrets.md b/docs/spec/X10-deploy-env-secrets.md new file mode 100644 index 0000000..30bd731 --- /dev/null +++ b/docs/spec/X10-deploy-env-secrets.md @@ -0,0 +1,86 @@ +# X10 — Deploy, סביבה וסודות (Deploy, Environment & Secrets) + +קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **קונפיגורציה, משתני-סביבה +וסודות** — מה שהיה מכוסה כחצי-deploy בלבד ב-[X3 §2](X3-integration-deploy.md). הוא מגדיר את חוזה-ה-env +(SSoT אחד), מקור-ה-config (Coolify), טיפול-הסודות, ואי-ה-hardcode. X3 נשאר הבעלים של **זרימות**-האינטגרציה; +X10 הבעלים של **הקונפיגורציה וה-deploy**. + +> **invariant פרויקטלי-תפעולי + הנדסי.** ENV1/ENV3/ENV4/ENV5 נשענים על עקרונות-הנדסה מוכרים (12-Factor, +> ניהול-סודות) — ≥3 מקורות. ENV2 (מקור-config של *מערכת זו*) הוא תפעולי, נקשר ל-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). + +--- + +## 1. מצב קיים (מאומת מול הקוד) + +- **מודל-deploy:** legal-ai = Coolify Docker (UUID `gyjo0mtw2c42ej3xxvbz8zio`, build_pack `dockerimage`); + ה-env **מוזרק ישירות מ-Coolify**, לא מ-Infisical ([X3 §2](X3-integration-deploy.md); זיכרון `reference_legal_ai_env_architecture`). +- **40+ משתני-env** נקראים על-פני [config.py](../../mcp-server/src/legal_mcp/config.py), [web/app.py](../../web/app.py), + [paperclip_api.py](../../web/paperclip_api.py)/[paperclip_client.py](../../web/paperclip_client.py), + [gitea_client.py](../../web/gitea_client.py), [chat_proxy.py](../../web/chat_proxy.py). +- **קטלוג-UI** ([mcp_env_catalog.py](../../web/mcp_env_catalog.py)) מכסה **13 בלבד** מתוך ה-40+ → השאר בלתי-נראים + לדף-ההגדרות ולגילוי-drift. +- **Infisical:** קוד-ה-SDK ב-[config.py](../../mcp-server/src/legal_mcp/config.py) קורא `INFISICAL_TOKEN`, אך + בקונטיינר הוא **לעולם לא מוגדר** → קוד מת; ה-priority בפועל = Coolify-env בלבד. + +--- + +## 2. Invariants של התחום + +### INV-ENV1: env-catalog יחיד = SSoT לכל משתני-הסביבה +**כלל:** קיים **קטלוג-env יחיד** המתאר את **כל** המשתנים (שם, ברירת-מחדל, סוד?, מי-קורא, מה-שולט). אין משתנה +שנקרא-בקוד אך לא-בקטלוג, ואין משתנה-בקטלוג שלא-נקרא. מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) +ו-[G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש) (שלמות-הקטלוג). **הנדסי.** +**מקורות:** *The Twelve-Factor App — III. Config* (https://12factor.net/config) · OWASP — *Configuration / Secrets Management Cheat Sheet* +(https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html) · Kleppmann *DDIA* (config as data) | סטטוס: verified +**אכיפה:** קטלוג מקיף + בדיקה ש-getenv call-sites ⊆ קטלוג. **כיום:** 13/40+ בלבד ([gap-audit GAP-60](gap-audit.md)). +**הפרה ידועה:** `PAPERCLIP_BOARD_API_KEY`/`GITEA_*`/`CHAT_SERVICE_URL`/`LEGAL_CHAT_SHARED_SECRET` לא בקטלוג; `GITEA_ACCESS_TOKEN` מול `GITEA_TOKEN` (שני שמות) ([gap-audit GAP-58](gap-audit.md)). + +### INV-ENV2: מקור-config יחיד ומתועד (Coolify) — בלי קוד-מת +**כלל:** למערכת **מקור-config אחד מתועד** (Coolify-env לקונטיינר), והקוד אינו מניח מקור-שני שאינו פעיל. +אין "Infisical priority" מדומה כשאין `INFISICAL_TOKEN`. מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) +(מקור-אמת יחיד) וכלל "אין בליעה שקטה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). **פרויקטלי-תפעולי.** +**מקור-סמכות:** זיכרון `reference_legal_ai_env_architecture`; `feedback_infisical_coolify_drift`; [X3 §2](X3-integration-deploy.md). +**אכיפה:** לתעד Coolify כ-SSoT; להסיר/לבודד את קוד-ה-Infisical או להפעילו אמיתית. +**הפרה ידועה:** קוד-Infisical ב-[config.py](../../mcp-server/src/legal_mcp/config.py) מת בקונטיינר; ה-priority המתועד לא תואם מציאות ([gap-audit GAP-55](gap-audit.md)). + +### INV-ENV3: ללא hardcode — IDs/URLs/נתיבים מ-config +**כלל:** מזהים (company/agent), כתובות (Paperclip/Coolify/Gitea/chat/frontend), פורטים ונתיבים **נגזרים מ-config**, +לא קבועים בקוד. אין `/home/chaim` קשיח ואין UUID קשיח. מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) +(SSoT) — תואם [X7 INV-INT5](X7-paperclip-client-params.md). **הנדסי.** +**מקורות:** *Twelve-Factor App — III. Config* · *Twelve-Factor — X. Dev/prod parity* (https://12factor.net/dev-prod-parity) · +Google *SRE / configuration as data* (https://sre.google/workbook/configuration-design/) | סטטוס: verified +**אכיפה:** grep-gate נגד literals (UUID/URL/path) בקוד-חדש. **כיום אין.** +**הפרה ידועה:** UUIDs קשיחים ([paperclip_client.py:36-62](../../web/paperclip_client.py), [app.py:3976](../../web/app.py)); URLs קשיחים (`pc.nautilus...`, `coolify...`, `legal-ai-next...`); `LEGAL_AI_WORKSPACE_CWD="/home/chaim/legal-ai"`; chat-URL `10.0.1.1` מול תיעוד `host.docker.internal` ([gap-audit GAP-56/59/61](gap-audit.md)). + +### INV-ENV4: אין secrets בקוד/בברירות-מחדל — fail-loud +**כלל:** שום סוד (creds/key/token) אינו בקוד או בברירת-מחדל; היעדר-סוד **נכשל בקול** (לא נופל לברירת-מחדל +שקטה עם creds). אין סוד מודלף ל-log או ל-git. מופע של [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) +(integrity) וכלל "אין בליעה שקטה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). **הנדסי.** תואם זיכרון `feedback_secrets_first`. +**מקורות:** OWASP — *Secrets Management Cheat Sheet* (https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html) · +*Twelve-Factor — III. Config* (no secrets in code) · CWE-798 — *Use of Hard-coded Credentials* (https://cwe.mitre.org/data/definitions/798.html) | סטטוס: verified +**אכיפה:** ברירות-מחדל ריקות + כישלון-מפורש; secret-scan ב-CI. +**הפרה ידועה:** `PAPERCLIP_DB_URL` ברירת-מחדל `postgresql://paperclip:paperclip@...` (creds plaintext) ב-3 מקומות ([paperclip_client.py:21](../../web/paperclip_client.py), [app.py:3789,3964](../../web/app.py)) ([gap-audit GAP-57](gap-audit.md)). + +### INV-ENV5: drift-detection מכסה את כל המשתנים הקריטיים +**כלל:** מנגנון גילוי-ה-drift (Coolify↔container) מכסה את **כל** המשתנים הקריטיים, לא תת-קבוצה. מופע של +[G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן) ברוח-שלו (freshness של config) ו-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). **הנדסי.** +**מקורות:** *Twelve-Factor — III. Config* · Google *SRE — config drift* · HashiCorp — *config drift / desired state* (https://developer.hashicorp.com/well-architected-framework) | סטטוס: verified +**אכיפה:** הרחבת ה-catalog ל-drift-detection מלא בדף-ההגדרות. +**הפרה ידועה:** רק 13/40+ במנגנון; 8+ סודות קריטיים בלתי-מנוטרים ([gap-audit GAP-60](gap-audit.md)). + +--- + +## 3. Deploy — עמידוּת (מ-X3 §2, מורחב) +- **מחזור:** commit→push→Gitea Actions→Coolify redeploy (~2-4 דק'); endpoint חדש דורש גם `npm run api:types` ([X3 §2](X3-integration-deploy.md), [INV-INT2](X3-integration-deploy.md)). +- **חולשות-עמידוּת שנמצאו:** [start.sh](../../start.sh) **אינו נכשל** אם uvicorn לא עולה (ה-UI עולה עם בקאנד שבור); + ה-curl ל-Coolify ב-[.gitea/workflows/deploy.yaml](../../.gitea/workflows/deploy.yaml) הוא fire-and-forget (אין אימות-הצלחה) ([gap-audit GAP-62](gap-audit.md)). +- **host.docker.internal:** ה-chat-service נדרש דרך gateway; תיעוד מול קוד לא-תואמים (10.0.1.1) — ENV3. + +--- + +## 4. הפניות-אחיות +- [X3-integration-deploy.md](X3-integration-deploy.md) — זרימות-אינטגרציה + INV-INT2 (מחזור-deploy). +- [X7-paperclip-client-params.md](X7-paperclip-client-params.md) — IDs/keys של Paperclip (INV-INT5 תואם ENV3). +- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים), [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש), [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai), כלל "אין בליעה שקטה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). +- זיכרונות: `reference_legal_ai_env_architecture`, `feedback_infisical_coolify_drift`, `feedback_secrets_first`. +- [config.py](../../mcp-server/src/legal_mcp/config.py), [mcp_env_catalog.py](../../web/mcp_env_catalog.py), [Dockerfile](../../Dockerfile), [start.sh](../../start.sh), [.env.example](../../.env.example). diff --git a/docs/spec/X3-integration-deploy.md b/docs/spec/X3-integration-deploy.md index 299b253..7a56f4a 100644 --- a/docs/spec/X3-integration-deploy.md +++ b/docs/spec/X3-integration-deploy.md @@ -80,6 +80,9 @@ PUT /api/cases/{n} → [BackgroundTask] emit_case_status_webhook() ([paperclip_api.py:120-165](../../web/paperclip_api.py)) ו-`emit_export_complete_webhook` ([paperclip_api.py:168+](../../web/paperclip_api.py)). +> **חוזה ה-webhook (idempotency / at-least-once / אירוע מגורס)** מפורט ב-[X7 INV-INT7/INT8](X7-paperclip-client-params.md): +> ה-emitter הנוכחי fire-and-forget בולע שגיאות וללא event-id/dedup — יעד FU-9. + ### 1ד. כל קריאת-API דרך helper — לא curl/httpx ישיר קריאות ל-Paperclip עוברות תמיד דרך helper, לא דרך לקוח גולמי: @@ -97,6 +100,9 @@ PUT /api/cases/{n} → [BackgroundTask] emit_case_status_webhook() ## 2. מודל ה-Deploy — שני מודלים דו-קיימים +> **קונפיגורציה, env וסודות** — ה-deep-dive המלא (catalog ה-env, מקור-config, secrets, hardcode, +> drift) ב-[X10-deploy-env-secrets.md](X10-deploy-env-secrets.md). כאן נשאר רק מודל-ההרצה. + על שרת Nautilus דרים **שני מודלי-הרצה**. ערבוב ביניהם הוא הטעות הנפוצה ביותר ([root CLAUDE.md](../../../CLAUDE.md) "Deploy architecture"; [legal-ai/CLAUDE.md](../../CLAUDE.md) "ארכיטקטורת Deploy"). @@ -210,3 +216,5 @@ audit-trail עקבי). - [.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. +- [X7-paperclip-client-params.md](X7-paperclip-client-params.md) — שכבת-הלקוח + פרמטרי-החיבור (INV-INT4–INT8). +- [X10-deploy-env-secrets.md](X10-deploy-env-secrets.md) — env/secrets/deploy deep-dive (INV-ENV1–ENV5). diff --git a/docs/spec/X4-agents.md b/docs/spec/X4-agents.md index 89190ba..02d0627 100644 --- a/docs/spec/X4-agents.md +++ b/docs/spec/X4-agents.md @@ -60,6 +60,19 @@ Paperclip בקונפליקט (project-specific מנצח default), אך אינו - **company_id פר-סוכן.** כל שורה בטבלה מיוצגת פעמיים (CMP + CMPA); ה-CEO לכל חברה שונה ([X2 §1](X2-multi-company.md)). הסוכן פועל רק בטווח-החברה שלו ([X2 §2](X2-multi-company.md)). +### 2א. מפת-הרשאות (tool grants) — frontmatter מול הוראות + +כל קובץ-סוכן מצהיר ב-frontmatter `tools:` (כולם: `Read/Bash/Grep/Glob` + תת-קבוצת `mcp__legal-ai__*`). +מפת-ההרשאות חייבת **לתאום** את מה שהוראות-הסוכן מצריכות ([X9 INV-TOOL6](X9-mcp-tool-contract.md), INV-AG3 להלן). +פערים שנמצאו (אומת 31.5.2026 — `legal-analyst.md` לא מעניק את 3 הכלים; תיקון ב-**FU-13**): + +| סוכן | כלי-חילוץ שההוראות דורשות אך **לא** מוענקים | +|------|---------------------------------------------| +| legal-analyst | `aggregate_claims_to_arguments`, `extract_references`, `extract_internal_citations` | +| legal-researcher | `precedent_extract_halachot`, `precedent_extract_metadata`, `precedent_process_pending` | + +→ [gap-audit GAP-46](gap-audit.md). + --- ## 3. סוכני-התהליך (תת-פרויקט 5) — סעיף שמור (RESERVED) @@ -113,6 +126,14 @@ CMPA→8xxx/9xxx). אסור ליצור פרויקט/issue/תוכן לתיק מח another company`, [X2 §2](X2-multi-company.md)). **הפרה ידועה:** — +### INV-AG3: מפת-ההרשאות תואמת את הוראות-הסוכן — לא חסר ולא עודף +**כלל:** ה-frontmatter `tools:` של כל סוכן מעניק **בדיוק** את הכלים שהוראותיו דורשות — כל כלי שההוראות +מצריכות מוענק, וכלי שמוענק-ולא-בשימוש נבחן. מופע של [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) +(שערים מוגדרים) ו-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים); מקביל ל-[X9 INV-TOOL6](X9-mcp-tool-contract.md). +**מקור-סמכות:** frontmatter `tools:` מול ה-instructions בקבצי-[.claude/agents/](../../.claude/agents/). (פרויקטלי-תפעולי.) +**אכיפה:** בדיקת-עקביות tools↔instructions (יעד FU-13). **כיום אין.** +**הפרה ידועה:** legal-analyst חסר `aggregate_claims_to_arguments`/`extract_references`/`extract_internal_citations`; researcher חסר טריגרי-חילוץ (§2א; [gap-audit GAP-46](gap-audit.md)). + --- ## 5. חיווט הספ לסוכנים — בוצע (FU-8b) @@ -143,3 +164,5 @@ another company`, [X2 §2](X2-multi-company.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 (סינון-חברה, זרימה). +- [X9-mcp-tool-contract.md](X9-mcp-tool-contract.md) — חוזה-הכלים שההרשאות (INV-AG3 / §2א) מעניקות. +- [skills/](../../skills/) — 5 skills (decision, assistant, docx, dafna-decision-template, new-company-setup); עקביות-skills↔סוכן + dedup → FU-13. diff --git a/docs/spec/X6-ui-api-contract.md b/docs/spec/X6-ui-api-contract.md new file mode 100644 index 0000000..0997662 --- /dev/null +++ b/docs/spec/X6-ui-api-contract.md @@ -0,0 +1,108 @@ +# X6 — חוזה UI↔API וכללי-עיצוב הממשק (UI↔API Contract & Design Rules) + +קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **הממשק (web-ui) וחוזה +ה-API בינו לבקאנד** — שלא היה מכוסה בספ עד כה. הוא מגדיר: (א) חוזה-הקשר פרונט↔בק (OpenAPI כ-SSoT, +מודלי-תשובה, envelope, SSE, טיפול-שגיאות); (ב) **כללי-עיצוב הממשק** — מקור-אמת יחיד ל-enums/תוויות, +helpers משותפים, וחוזה-טופס לכל סוג-מסמך. הממצאים בפועל מתועדים ב-[ui-audit.md](ui-audit.md). + +> **שני סוגי invariant כאן.** UI1–UI5 הם **הנדסיים** (חוזה-API/קליינט כללי — ≥3 מקורות + סטטוס). +> UI6 (חוזה-טופס) הוא **פרויקטלי-תפעולי**, נגזר מ-[X8](X8-field-provenance.md), ומשרת +> [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai). + +--- + +## 1. ארכיטקטורה קיימת + +- **web-ui** — Next.js 16 + TS + Tailwind v4 + shadcn + TanStack Query. 13 דפים (ראה [ui-audit.md](ui-audit.md)). +- **Proxy** — [next.config.ts](../../web-ui/next.config.ts): `/api/*` → `NEXT_PUBLIC_API_ORIGIN` (ברירת-מחדל `http://127.0.0.1:8000`); `/openapi.json` → schema של ה-FastAPI. +- **לקוח** — [client.ts](../../web-ui/src/lib/api/client.ts): `apiRequest` + `ApiError` + `makeQueryClient`. 18 מודולי-API. +- **טיפוסים** — [types.ts](../../web-ui/src/lib/api/types.ts) (auto-gen `openapi-typescript`, 124 operations). `npm run api:types`. +- **SSE** — [sse.ts](../../web-ui/src/lib/sse.ts): `openSSE` (progress של העלאות/עיבוד). +- **בקאנד** — [web/app.py](../../web/app.py): 143 endpoints, מונוליטי, **~60% ללא Pydantic response model**. + +--- + +## 2. Invariants של התחום + +### INV-UI1: ה-OpenAPI schema הוא ה-SSoT לחוזה — טיפוסי-לקוח נגזרים, לא ידניים-סוטים +**כלל:** חוזה ה-API מוגדר **פעם אחת** ב-OpenAPI (שמופק מהבקאנד); טיפוסי-ה-frontend **נגזרים** ממנו +(`openapi-typescript`), ואינם מתוחזקים ידנית במקביל. אין "טיפוס-מראה" מקומי שמשכפל endpoint וסוטה ממנו. +מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) (מקור-אמת יחיד). +**מקורות:** OpenAPI Specification 3.1 (single contract / source of truth; JSON-Schema 2020-12) +(https://spec.openapis.org/oas/latest.html) · Pact — *consumer-driven contract testing* +(https://docs.pact.io/) · Speakeasy — *Pact vs OpenAPI* (provider-driven SSoT) +(https://www.speakeasy.com/blog/pact-vs-openapi) | סטטוס: verified +**אכיפה:** `npm run api:types` ב-CI; איסור טיפוסי-מראה ידניים. **כיום אין** — ה-frontend מתחזק טיפוסים ידניים. +**הפרה ידועה:** [cases.ts:1-9](../../web-ui/src/lib/api/cases.ts) מתעד מפורשות שה-`/api/cases` מחזיר `unknown` +ולכן מוחזק טיפוס `CaseDetail` ידני; `PracticeArea` מוגדר ב-3 מקומות עם ערכים שונים ([ui-audit.md](ui-audit.md), [gap-audit GAP-30/31](gap-audit.md)). + +### INV-UI2: לכל endpoint נצרך — response model מפורש (חוזה-שלמות API) +**כלל:** כל endpoint שה-UI צורך נושא **response model מפורש** (Pydantic), כך ש-OpenAPI מפיק טיפוס אמיתי +(לא `unknown`/`object`). זהו פאֶט של [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש) (שלמות-חוזה לפני צריכה). +**מקורות:** OpenAPI 3.1 (schema objects) · Zalando *RESTful API Guidelines* (explicit schemas) +(https://opensource.zalando.com/restful-api-guidelines/) · FastAPI *Response Model* docs +(https://fastapi.tiangolo.com/tutorial/response-model/) | סטטוס: verified +**אכיפה:** linter/CI שמסמן endpoint נצרך ללא response_model. **כיום אין** — ~60% מהendpoints ללא מודל. +**הפרה ידועה:** רוב ה-endpoints ב-[app.py](../../web/app.py) מחזירים dict חופשי → `unknown` ב-types.ts ([gap-audit GAP-30](gap-audit.md)). + +### INV-UI3: envelope-תשובה ושגיאה עקבי על-פני ה-API +**כלל:** כל ה-endpoints חולקים **מבנה-תשובה ומבנה-שגיאה אחיד** (לא string-לפעמים-JSON-לפעמים). שגיאות +לפי תבנית סטנדרטית (Problem Details). מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). +**מקורות:** RFC 9457 — *Problem Details for HTTP APIs* +(https://www.rfc-editor.org/rfc/rfc9457) · Zalando *RESTful API Guidelines* (consistent responses) · +Microsoft *REST API Guidelines* (error structure) +(https://github.com/microsoft/api-guidelines) | סטטוס: verified +**אכיפה:** envelope משותף ב-app.py + handler-שגיאות גלובלי. **כיום אין** — מעורב string/JSON/`{error}`/`{detail}`. +**הפרה ידועה:** [search.py](../../web/app.py) מחזיר `"לא נמצאו תוצאות."` או JSON; חלק מהכלים `{error:...}`, חלק raise ([gap-audit GAP-32](gap-audit.md), [X9 INV-TOOL1](X9-mcp-tool-contract.md)). + +### INV-UI4: אין בליעת-שגיאה ב-UI +**כלל:** כל מצב-שגיאה (fetch/mutation) **מוצג או מטופל מפורשות** — error boundary ו/או טיפול ב-`error` +של `useQuery`/`useMutation`. אין כשל שקט שמשאיר את המשתמש בלי משוב. תואם כלל "אין בליעה שקטה" +([חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). +**מקורות:** React docs — *Error Boundaries* +(https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) · +TanStack Query — *Error handling* (https://tanstack.com/query/latest/docs/framework/react/guides/query-functions#handling-and-throwing-errors) · +Nielsen Norman Group — *Error-Message Guidelines* (https://www.nngroup.com/articles/error-message-guidelines/) | סטטוס: verified +**אכיפה:** error boundary ברמת-האפליקציה + רכיב-שגיאה משותף; code-review. **כיום חלקי** — חלק מהדפים אינם +מטפלים ב-`error`; כרטיסי-שגיאה משוכפלים ולא-עקביים. +**הפרה ידועה:** [ui-audit.md](ui-audit.md) — כרטיס-שגיאה משוכפל ×3, fallback של SSE שמסתיר כישלון כ-"completed" ([gap-audit GAP-32/33](gap-audit.md)). + +### INV-UI5: חוזה-SSE/progress עם terminal states מוגדרים +**כלל:** ערוץ ה-progress (SSE) נושא **terminal states מפורשים** (completed/failed/timeout). אין הנחת-השלמה +שקטה על timeout; אי-התאמות-TTL (frontend↔backend) נמנעות. נקשר ל-freshness ([G6](00-constitution.md#inv-g6-re-index-בכל-שינוי-תוכן)). +**מקורות:** WHATWG HTML — *Server-Sent Events / EventSource* (https://html.spec.whatwg.org/multipage/server-sent-events.html) · +MDN — *Using server-sent events* (https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) · +TanStack Query — *Important Defaults* (staleTime/refetch) (https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults) | סטטוס: verified +**אכיפה:** סכמת-אירוע SSE עם terminal state מפורש; יישור TTL. **כיום:** fallback של 10ש' מניח completed. +**הפרה ידועה:** [documents.ts:226-232](../../web-ui/src/lib/api/documents.ts) — timeout→`{status:"completed"}`; TTL 5ש' front מול 300ש' redis ([gap-audit GAP-33](gap-audit.md)). + +### INV-UI6: חוזה-טופס מוצהר לכל סוג-מסמך + שיקוף מקור-המילוי +**כלל:** לכל סוג-מסמך (מסמך-תיק / פסיקה חיצונית / החלטה פנימית) יש **חוזה-טופס מוצהר** — אילו שדות, +חובה/רשות/אוטו/pending/editable — **נגזר מ-[X8](X8-field-provenance.md)**; וה-UI **משקף את מקור-המילוי** +(מסמן מה חולץ אוטומטית/ע"י-Opus מול מה שהיו"ר הזין), כדי שהיו"ר ידע מה לאמת. מופע של +[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) (שקיפות-מקור). **invariant פרויקטלי-תפעולי.** +**מקור-סמכות:** [X8-field-provenance.md](X8-field-provenance.md) (טבלת-ה-provenance); feedback היו"ר. +**אכיפה:** רכיב-טופס נגזר-X8 + אינדיקציית "מולא-ע"י-Opus"/"ממתין"/`searchable`. **כיום אין** — שדות-Opus +מוצגים כשדות-עריכה רגילים ללא סימון. +**הפרה ידועה:** [precedents/[id]/page.tsx](../../web-ui/src/app/precedents/%5Bid%5D/page.tsx) — `summary`/`headnote`/`key_quote` ללא חיווי-מקור; אין חיווי `searchable` ([gap-audit GAP-36](gap-audit.md)). + +--- + +## 3. כללי-עיצוב (Design Rules) — נגזרים מה-invariants +- **SSoT ל-enums/תוויות/tones:** כל enum (CaseStatus, PracticeArea, AppealSubtype, DocType, outcome) + + תוויותיו + צבעיו מוגדרים **פעם אחת** ונצרכים מיבוא — לא משוכפלים בין דפים/רכיבים (מופע UI1/G2). +- **helpers משותפים:** פירמוט-תאריך, builder ל-FormData (העלאות), רכיב-שגיאה, query-config (intervals) — + משותפים, לא מועתקים. +- **חוזי-טופס:** ראה INV-UI6 ([X8](X8-field-provenance.md)). + +הממצאים הקונקרטיים (כפילויות, הגדרות-שגויות, redundancy) ב-[ui-audit.md](ui-audit.md); התיקון — **FU-10**. + +--- + +## 4. הפניות-אחיות +- [ui-audit.md](ui-audit.md) — audit דף-אחר-דף (13 דפים) בתבנית-ה-gap. +- [X8-field-provenance.md](X8-field-provenance.md) — מקור-מילוי-שדות (בסיס ל-INV-UI6). +- [X7-paperclip-client-params.md](X7-paperclip-client-params.md) — חוזה-ה-API שהפלאגין צורך. +- [X9-mcp-tool-contract.md](X9-mcp-tool-contract.md) — חוזה-envelope מקביל בכלי-ה-MCP. +- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים), [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש), [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai), כלל "אין בליעה שקטה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). +- [web-ui/next.config.ts](../../web-ui/next.config.ts), [client.ts](../../web-ui/src/lib/api/client.ts), [types.ts](../../web-ui/src/lib/api/types.ts), [sse.ts](../../web-ui/src/lib/sse.ts). diff --git a/docs/spec/X7-paperclip-client-params.md b/docs/spec/X7-paperclip-client-params.md new file mode 100644 index 0000000..761ed61 --- /dev/null +++ b/docs/spec/X7-paperclip-client-params.md @@ -0,0 +1,155 @@ +# X7 — לקוח-Paperclip ופרמטרי-חיבור (Paperclip Client & Connection Parameters) + +קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) ומשלים את [X3](X3-integration-deploy.md): +בעוד X3 מתאר את **זרימות**-האינטגרציה (wakeup, ניתוב comments, webhook), קובץ זה הוא ה-deep-dive +על **שכבת-הלקוח והפרמטרים** — *איך* legal-ai מדבר עם Paperclip בקוד (אילו לקוחות, אילו מסלולים), +ועל **כל הפרמטרים המחברים** (מזהי-חברה/סוכן, env, מפתחות, `plugin_state`, גזירת `company_id`). + +> **invariant פרויקטלי-תפעולי.** ה-invariants כאן הם עובדות על איך *מערכת זו* בנויה — אין להן +> סמכות חיצונית; מקור-הסמכות = ה-runbooks והקוד ([root CLAUDE.md](../../../CLAUDE.md), +> [legal-ai/CLAUDE.md](../../CLAUDE.md), [web/paperclip_api.py](../../web/paperclip_api.py), +> [web/paperclip_client.py](../../web/paperclip_client.py)). כל invariant **נקשר** ל-G גלובלי שהוא משרת — +> כאן בעיקר [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) (מסלול קנוני יחיד) +> ו-[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) (עקיבוּת/audit), וכלל-ההנדסה "סימטריה" ([חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). + +--- + +## 1. מצב קיים — שני לקוחות מקבילים + +ל-legal-ai יש **שני לקוחות Paperclip שונים** שחיים בו-זמנית, וזהו מקור-השורש לרוב הפערים כאן: + +| לקוח | קובץ | אופי | מה מנהל | +|------|------|------|---------| +| "current" (API) | [web/paperclip_api.py](../../web/paperclip_api.py) | HTTP דרך `pc_request` + board API key | webhooks יוצאים, wakeup חלקי | +| "legacy" (DB-ישיר) | [web/paperclip_client.py](../../web/paperclip_client.py) | **חיבור psql ישיר** ל-DB של Paperclip + API | projects, issues, comments, wakeup, queries | + +[legal-ai/CLAUDE.md](../../CLAUDE.md) מתעד ש-`paperclip_client.py` הוא "legacy — השתמש ב-paperclip_api.py", +אך בפועל ה-legacy עדיין מבצע את **רוב העבודה הכבדה** (יצירת תיקים/issues, comments, wakeup-ים), +וחלקו דרך **`INSERT`/`SELECT` ישיר** ל-DB של Paperclip — מסלול-מקביל לעוקף את ה-API. + +זוהי בדיוק התבנית ש-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) אוסר: +שני מסלולי-קוד מקבילים ליכולת אחת (גישה ל-Paperclip), שמתפצלים ועלולים לסטות. + +--- + +## 2. הפרמטרים המחברים (Connection Parameters) + +### 2א. משתני-סביבה +| Var | קורא | ברירת-מחדל | סוד? | +|-----|------|-----------|------| +| `PAPERCLIP_API_URL` | [paperclip_api.py](../../web/paperclip_api.py) | `http://localhost:3100` | לא | +| `PAPERCLIP_BOARD_API_KEY` | paperclip_api.py / paperclip_client.py | `""` | **כן** (board key long-lived, לא JWT) | +| `PAPERCLIP_DB_URL` | [paperclip_client.py:21](../../web/paperclip_client.py), [app.py:3789](../../web/app.py) | `postgresql://paperclip:paperclip@127.0.0.1:54329/paperclip` | **כן — creds בתוך ברירת-המחדל** | +| `PAPERCLIP_COMPANY_ID` | [app.py:3976](../../web/app.py) | `42a7acd0-...` (CMP, hardcoded) | לא | +| `legalApiBaseUrl` | plugin (instance config) | `http://localhost:8085` | לא | + +> ראה גם [X10-deploy-env-secrets.md](X10-deploy-env-secrets.md) — חוזה-ה-env המלא וטיפול-הסודות. + +### 2ב. מזהים קשיחים בקוד (hardcoded) — סתירה ל-X3 +[paperclip_client.py:36-62](../../web/paperclip_client.py) מכיל **מזהי-חברה וסוכן קשיחים**: +- `COMPANIES["licensing"] = "42a7acd0-..."` (CMP), `COMPANIES["betterment"] = "8639e837-..."` (CMPA) +- CEO/curator/analyst UUIDs לכל חברה (CMP CEO `752cebdd-...`, וכו'). +- ה-plugin ([worker.ts](../../../plugin-legal-ai/src/worker.ts)) מכיל CEO IDs קשיחים משלו. + +זו **סתירה ישירה** ל-[X3 §1א](X3-integration-deploy.md) הקובע "מזהה-ה-CEO נגזר מ-`$PAPERCLIP_COMPANY_ID`, +**לעולם לא UUID hardcoded**". הסתירה מתועדת כממצא ([gap-audit GAP-26](gap-audit.md), וכן GAP-56 ב-X10). + +### 2ג. `plugin_state` keys (חוזה הקישור Paperclip↔legal-ai) +| `scope_kind` | `state_key` | ערך | משמעות | +|--------------|-------------|-----|--------| +| `issue` | `legal-case-number` | מספר-תיק | קישור issue→תיק | +| `issue` | `precedent-case-law-id` | case_law_id | קישור issue→פסיקה לחילוץ | +| `instance` | `webhook-idem-{requestId}` | timestamp | guard idempotency 5 דק' (inbound) | + +### 2ד. גזירת `company_id` — שתי דרכים שונות +- **app.py**: נגזר מ-prefix מספר-התיק (`1`→licensing, `8/9`→betterment) ([X3 §1ג](X3-integration-deploy.md)). +- **paperclip_client.py**: מ-`_FALLBACK_APPEAL_TYPE_TO_COMPANY` (מיפוי tag→company) + lookup ב-DB. + +שתי דרכי-גזירה לאותו ערך = drift פוטנציאלי ([gap-audit GAP-27](gap-audit.md)). + +--- + +## 3. צד נכנס (Inbound) — הפלאגין + +[plugin-legal-ai/src/worker.ts](../../../plugin-legal-ai/src/worker.ts) (לא בריפו זה) קורא ל-legal-ai דרך +`legalApiBaseUrl`. שלושה סוגי-משטח, שכולם חוזה-API שאינו מתועד היום ב-[X6](X6-ui-api-contract.md): +- **16 כלי `legal_*`** — עוטפים endpoints של `/api/cases/...`, `/api/search`, וכו'. +- **`onWebhook`** — מקבל את ה-webhook היוצא (ראה [X3 §1ג](X3-integration-deploy.md) ו-INV-INT8 להלן). +- **3 cron jobs** — `sync-case-status` (כל 15 דק'), `stale-case-reminder` (יומי), `weekly-feedback-analysis` (שבועי). + +--- + +## 4. Invariants של התחום + +### INV-INT4: לקוח-Paperclip קנוני יחיד — אין לקוח-מקביל ואין גישת-DB ישירה +**כלל:** כל גישה ל-Paperclip עוברת דרך **לקוח-API קנוני יחיד** (`pc_request`/`pc.sh`). **אסור** מסלול-מקביל — +לא לקוח שני, ולא `INSERT`/`SELECT`/`UPDATE` ישיר ל-DB של Paperclip. נתונים נקראים/נכתבים דרך ה-API +הרשמי בלבד; ה-DB של Paperclip הוא מקור-האמת של Paperclip, ו-legal-ai אינו מסלול-כתיבה מקביל אליו. +מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) וכלל "סימטריה" ([חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). +**מקור-סמכות:** [legal-ai/CLAUDE.md](../../CLAUDE.md) ("paperclip_client.py legacy — השתמש ב-paperclip_api.py"; +"קריאות API — תמיד דרך helper"); [X3 INV-INT3](X3-integration-deploy.md). (פרויקטלי-תפעולי — משרת G2.) +**אכיפה:** איחוד שני הלקוחות ללקוח-API אחד; הסרת `PAPERCLIP_DB_URL` כמסלול-כתיבה. **כיום אין אכיפה** — +שני הלקוחות דו-קיימים (יעד FU-9). +**הפרה ידועה:** [paperclip_client.py](../../web/paperclip_client.py) — `create_project`/`post_comment`-fallback +עושים `INSERT` ישיר ל-`projects`/`issues`/`comments`/`plugin_state` ([gap-audit GAP-24, GAP-25](gap-audit.md)). + +### INV-INT5: מזהי-חברה/סוכן מ-config — לא hardcoded בקוד +**כלל:** מזהי-החברה (CMP/CMPA) ומזהי-הסוכנים (CEO/curator/analyst) **נגזרים מ-config** (env/טבלת-מיפוי), +**לא** קבועים בקוד. הוספת חברה/החלפת instance אינה דורשת שינוי-קוד. מופע של +[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) (SSoT למיפוי) — מקור-אמת יחיד למיפוי. +**מקור-סמכות:** [X3 §1א](X3-integration-deploy.md) ("לעולם לא UUID hardcoded"); [X2-multi-company.md](X2-multi-company.md). +(פרויקטלי-תפעולי — משרת G2.) +**אכיפה:** טבלת-מיפוי/env יחידה; code-review. **כיום אין אכיפה** — UUIDs קשיחים. +**הפרה ידועה:** [paperclip_client.py:36-62](../../web/paperclip_client.py) + [app.py:3976](../../web/app.py) + +[plugin worker.ts](../../../plugin-legal-ai/src/worker.ts) — IDs קשיחים. **סותר את X3 §1א** ([gap-audit GAP-26](gap-audit.md)). + +### INV-INT6: גזירת `company_id` קנונית יחידה +**כלל:** ל-`company_id` יש **מסלול-גזירה אחד** מתוך מספר-התיק/סוג-הערר, במקום יחיד. אסור שתי לוגיקות-גזירה +מקבילות (prefix מול fallback-map) שעלולות לסטות. מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). +**מקור-סמכות:** [X3 §1ג](X3-integration-deploy.md); [X2-multi-company.md](X2-multi-company.md). (פרויקטלי-תפעולי.) +**אכיפה:** פונקציית-גזירה יחידה משותפת ל-app.py ול-client.py (יעד FU-9). **כיום אין.** +**הפרה ידועה:** prefix ב-[app.py](../../web/app.py) מול `_FALLBACK_APPEAL_TYPE_TO_COMPANY` ב-[paperclip_client.py](../../web/paperclip_client.py) ([gap-audit GAP-27](gap-audit.md)). + +### INV-INT7: webhook יוצא — at-least-once + idempotency + ללא בליעה שקטה +**כלל:** ה-webhook היוצא (legal-ai→plugin) מספק **at-least-once** עם **מפתח-idempotency יציב** (event id), +כך שמסירה-כפולה בטוחה בצד-המקבל; וכישלון-מסירה **נרשם ומדווח** (telemetry/health), לא נבלע בשקט. +זהו invariant **הנדסי** (סמנטיקת-מסירה כללית), הקשור ל-[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) +(עקיבוּת) ולכלל "אין בליעה שקטה" ([חוקה §6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). +**מקורות:** Stripe — *Webhooks / at-least-once delivery & idempotency* +(https://docs.stripe.com/webhooks) · Hookdeck — *At-Least-Once vs Exactly-Once Webhook Delivery* +(https://hookdeck.com/webhooks/guides/webhook-delivery-guarantees) · Martin Kleppmann, *DDIA* +(O'Reilly 2017, idempotence & exactly-once semantics) | סטטוס: verified +**אכיפה:** event-id יציב + UNIQUE-dedup בצד-המקבל; ה-emitter רושם כישלון ל-telemetry (יעד). **כיום:** +inbound יש guard 5 דק' ([X3 §1ג](X3-integration-deploy.md)); **outbound אין idempotency**, וה-emitter בולע +שגיאות ב-`logger.warning` בלבד. +**הפרה ידועה:** `emit_*_webhook` ב-[paperclip_api.py](../../web/paperclip_api.py) — fire-and-forget, `try/except` +שמתעד warning ולעולם לא raise, ללא event-id/dedup ([gap-audit GAP-28](gap-audit.md)). + +### INV-INT8: חוזה-אירועי-webhook מתוקען ומגורס +**כלל:** ל-webhook חוזה-אירוע **מפורש ומגורס** — `eventType` מתוך קבוצה סגורה, סכמת-payload מתועדת לכל +סוג, וגרסה. אין `eventType` חופשי ואין "ברירת-מחדל שקטה". מופע של +[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים)/[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai). +**מקור-סמכות:** [X3 §1ג](X3-integration-deploy.md) (3 סוגי-האירוע: `status_change`, `missing_precedent_created`, +`export_complete`); קוד ה-emitter ([paperclip_api.py:87+](../../web/paperclip_api.py)). (פרויקטלי-תפעולי — משרת G2/G9.) +**אכיפה:** enum + סכמה משותפים emitter↔handler. **כיום:** `eventType` נופל ל-`status_change` כברירת-מחדל +אם חסר/לא-מוכר ([gap-audit GAP-29](gap-audit.md)). + +--- + +## 5. מצב קיים מול יעד — פער אכיפה +האינטגרציה נשענת על **נוהל + שני לקוחות**, לא על מסלול-קוד קנוני אחד: +- **לקוח (INV-INT4):** יעד — לקוח-API יחיד; הסרת מסלול-ה-DB הישיר. +- **מזהים (INV-INT5/INT6):** יעד — טבלת-מיפוי/env יחידה; פונקציית-גזירה אחת. +- **webhook (INV-INT7/INT8):** יעד — event-id + dedup + enum-אירוע מגורס + רישום-כישלון. + +כל אלה מקובצים ל-**FU-9** ([gap-audit.md](gap-audit.md)). + +--- + +## 6. הפניות-אחיות +- [X3-integration-deploy.md](X3-integration-deploy.md) — זרימות (wakeup, comments, webhook) + INV-INT1/2/3. +- [X10-deploy-env-secrets.md](X10-deploy-env-secrets.md) — חוזה-env מלא, סודות, hardcoded IDs/creds. +- [X2-multi-company.md](X2-multi-company.md) — CMP/CMPA, sync, company filtering. +- [X6-ui-api-contract.md](X6-ui-api-contract.md) — חוזה ה-API שהפלאגין (inbound) צורך. +- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים), [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai), כלל "סימטריה" ([§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). +- [web/paperclip_api.py](../../web/paperclip_api.py), [web/paperclip_client.py](../../web/paperclip_client.py), [scripts/pc.sh](../../scripts/pc.sh). diff --git a/docs/spec/X8-field-provenance.md b/docs/spec/X8-field-provenance.md new file mode 100644 index 0000000..6286c24 --- /dev/null +++ b/docs/spec/X8-field-provenance.md @@ -0,0 +1,118 @@ +# X8 — כללי-מילוי-שדות וחילוץ (Field-Population & Extraction Rules) + +קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-**SSoT לכללים שכרגע סמויים בקוד**: +כשמעלים החלטה/פסק-דין/מסמך-תיק — *איזה שדה מתמלא מאיזה מקור*, ומה הכללים על-גבי זה (אי-דריסת +ערך-יו"ר, שער-אישור, ציטוט-verbatim). הכללים האלה חיים היום מפוזרים על-פני 4 שירותים; כאן הם מאוחדים. +הוא משלים את [01-ingest.md](01-ingest.md) (הפייפליין) ו-[02-data-model.md](02-data-model.md) (הסכמה), +ומזין את [X6 INV-UI6](X6-ui-api-contract.md) (שיקוף-מקור ב-UI). + +> **מודלי-סמכות מעורבים.** FP1 ו-FP4 הם **הנדסיים** (lineage/integrity — ≥3 מקורות). FP2/FP3/FP5 הם +> **פרויקטלי-תפעוליים** הנקשרים ל-[G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) +> (שער אנושי) ו-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). + +--- + +## 1. ארבעת מקורות-המילוי + +| מקור | הגדרה | דוגמאות | +|------|-------|---------| +| **DETERMINISTIC** | parse של שם-קובץ / מטא-PDF / OCR / regex — ללא LLM | `full_text`, `extraction_status`, `source_kind`, chunks, page_number | +| **OPUS-ANALYSIS** | Claude Opus קורא את כל המסמך, ממלא **רק שדה ריק/placeholder**, אסינכרוני | `headnote`, `summary`, `key_quote`, `subject_tags`, `case_name`, `court`, `date`, `appeal_subtype`, `precedent_level`, `source_type`, `citation_formatted`, halachot | +| **CHAIR-MANUAL** | היו"ר מזין בטופס; חובה או רשות | `citation`/`case_number` (חובה), והשאר נשאר לעריכה | +| **DERIVED** | מחושב משדות אחרים | `district` מ-court, `proceeding_type` מ-appeal_subtype, `searchable` | + +--- + +## 2. טבלת-provenance לפי סוג-מסמך (ה-SSoT) + +> מאומת מול [precedent_metadata_extractor.py](../../mcp-server/src/legal_mcp/services/precedent_metadata_extractor.py), +> [halacha_extractor.py](../../mcp-server/src/legal_mcp/services/halacha_extractor.py), +> [ingest.py](../../mcp-server/src/legal_mcp/services/ingest.py), [db.py](../../mcp-server/src/legal_mcp/services/db.py). + +### 2א. פסיקה חיצונית (`case_law`, source_kind=`external_upload`) +| שדה | מקור | הערה | +|-----|------|------| +| `case_number` (citation) | CHAIR (חובה) | מפתח idempotency | +| `full_text`, `extraction_status`, `source_kind` | DETERMINISTIC | — | +| `case_name`, `court`, `date`, `headnote`, `summary`, `key_quote`, `subject_tags`, `appeal_subtype`, `precedent_level`, `source_type`, `citation_formatted` | CHAIR או OPUS | Opus ממלא רק אם ריק | +| `is_binding` | CHAIR (default true) | קובע prompt-הלכה | +| chunks (`content`/`section_type`/`page_number`) | DETERMINISTIC | — | +| `embedding` (chunks) | Voyage (לא-LLM-reasoning) | ⚠ לא-GENERATED ([gap-audit GAP-09](gap-audit.md)) | +| כל `halachot` | OPUS | נכנס pending_review | + +### 2ב. החלטה פנימית (`case_law`, source_kind=`internal_committee`) +כמו 2א, ובנוסף: `case_number` **חובה**; `chair_name`/`district`/`proceeding_type` — CHAIR או OPUS או DERIVED; +`source_type` = `appeals_committee` (DETERMINISTIC קבוע). placeholder `"(טרם חולץ)"` מסומן ל-chair_name/district +ריקים ומטופל כריק ע"י ה-extractor. + +### 2ג. מסמך-תיק (`documents`) +| שדה | מקור | +|-----|------| +| `case_id`, `title` | CHAIR | +| `doc_type` | DETERMINISTIC (local_classifier) → fallback Claude אם confidence<0.8 | +| `extracted_text`, `extraction_status`, `page_count` | DETERMINISTIC | +| chunks + `embedding` | DETERMINISTIC + Voyage | +| claims / appraiser_facts | OPUS (כלי-חילוץ נפרדים — ראה [X9](X9-mcp-tool-contract.md)) | + +--- + +## 3. Invariants של התחום + +### INV-FP1: לכל שדה מקור-מילוי מוצהר — הטבלה היא ה-SSoT +**כלל:** לכל שדה-מטא יש **מקור-מילוי מוצהר** (deterministic / opus / chair / derived), ב**מקום יחיד** +(טבלת §2). אין כללי-מילוי סמויים מפוזרים בין שירותים. מופע של +[G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) (lineage — מאיפה כל ערך). **הנדסי.** +**מקורות:** ISO 8000-110 (data quality — provenance) · DAMA-DMBOK2 (data lineage) · OpenLineage spec +(https://openlineage.io/) | סטטוס: verified +**אכיפה:** טבלת-provenance מוצהרת (§2) + עמודת-מקור-מילוי לכל שדה-נגזר (יעד; ראה [02-data-model.md](02-data-model.md)). +**הפרה ידועה:** הכללים מפוזרים על precedent_metadata_extractor/halacha_extractor/ingest/recompute_searchable; אין SSoT ([gap-audit GAP-35](gap-audit.md)). + +### INV-FP2: חילוץ-LLM אינו דורס ערך שהוזן ידנית +**כלל:** חילוץ-Opus ממלא **רק שדה ריק/placeholder** — ערך שהיו"ר הזין **לעולם אינו נדרס**. סמכות-התוכן +היא היו"ר. מופע של [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant). **פרויקטלי-תפעולי.** +**מקור-סמכות:** [precedent_metadata_extractor.py](../../mcp-server/src/legal_mcp/services/precedent_metadata_extractor.py) +(`apply_to_record` — compare-to-empty); feedback היו"ר. (משרת G10.) +**אכיפה:** לוגיקת compare-to-empty ב-extractor; convention placeholder מתועד. +**הפרה ידועה:** placeholder `"(טרם חולץ)"` כמחרוזת-קסם לא-מתועדת/שבירה ([gap-audit GAP-37](gap-audit.md)). + +### INV-FP3: פלט-LLM נכנס כ-pending — רק אישור-יו"ר הופך אותו לשמיש +**כלל:** פלט-חילוץ של LLM (הלכות; ובהמשך גם טענות-משפטיות) נכנס במצב **לא-מאושר** (`pending_review`), +ואינו נחשף לחיפוש/החלטה עד **אישור-יו"ר**. מופע של [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) +(שער אנושי) — תואם [05-qa-review.md](05-qa-review.md). **פרויקטלי-תפעולי.** +**מקור-סמכות:** [halacha_extractor.py](../../mcp-server/src/legal_mcp/services/halacha_extractor.py) (review_status); [01-ingest.md](01-ingest.md). +**אכיפה:** `review_status` חוסם חיפוש עד `approved`/`published`. +**הפרה ידועה:** `legal_arguments` **חסר** שער-אישור מקביל ([gap-audit GAP-39](gap-audit.md); [02-data-model.md](02-data-model.md)). + +### INV-FP4: supporting_quote חייב להיות verbatim +**כלל:** כל ציטוט-תומך (`supporting_quote` של הלכה, `key_quote`) חייב להופיע **מילה-במילה** בטקסט-המקור; +אחרת מסומן (`quote_verified=false`). מופע של [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai) +(integrity). **הנדסי.** +**מקורות:** ISO 15489-1:2016 (records integrity/authenticity) · RAG attribution (Lewis et al., 2020, NeurIPS) · +NCSC/JTC — *AI in Courts* (verifiable citation) | סטטוס: verified +**אכיפה:** `proofreader.verify_quote` בעת חילוץ → `quote_verified`. +**הפרה ידועה:** — (קיים; ה-flag נכתב, אך אין חיווי ב-UI — ראה [X6 INV-UI6](X6-ui-api-contract.md)). + +### INV-FP5: חילוץ אסינכרוני דרך claude_session מקומי +**כלל:** חילוץ-LLM (מטא, הלכות) רץ **אסינכרוני, מתור**, דרך `claude_session` **מקומי בלבד** — לא חוסם את +ה-web, ולא קורא ל-LLM מהקונטיינר. מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) +(מסלול-LLM קנוני יחיד). **פרויקטלי-תפעולי.** תואם זיכרון `feedback_claude_session_local_only`. +**מקור-סמכות:** [ingest.py](../../mcp-server/src/legal_mcp/services/ingest.py) (queue בצעד 12 → `process_pending_extractions`); [legal-ai/CLAUDE.md](../../CLAUDE.md) (claude_session local-only). +**אכיפה:** queue + `precedent_process_pending`; קריאות-LLM רק מ-MCP מקומי. +**הפרה ידועה:** תור-החילוץ **סמוי** (אין הבחנה pending-initial מול pending-review; אין extraction-job table) ([gap-audit GAP-45](gap-audit.md); [X9](X9-mcp-tool-contract.md)). + +--- + +## 4. חוזה-searchable (תזכורת — מוגדר ב-02) +רשומת `case_law` היא `searchable` רק כשמתקיים חוזה-השלמות ([G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש), +[02-data-model.md](02-data-model.md), FU-2a): ≥1 chunk עם embedding · `extraction_status='completed'` · +`case_number`/`source_kind` לא-ריקים · practice_area (לפנימי) · ≥1 שדה-מטא ({headnote/summary/subject_tags}). +ה-UI חייב **לשקף** את ה-flag הזה ([X6 INV-UI6](X6-ui-api-contract.md)). + +--- + +## 5. הפניות-אחיות +- [01-ingest.md](01-ingest.md) — הפייפליין הקנוני (12 צעדים) שבו החילוץ יושב. +- [02-data-model.md](02-data-model.md) — סכמת השדות + חוזה-searchable + ישויות-נגזרות. +- [X6 INV-UI6](X6-ui-api-contract.md) — שיקוף מקור-המילוי ב-UI. +- [X9-mcp-tool-contract.md](X9-mcp-tool-contract.md) — כלי-החילוץ (claims/appraiser_facts/halachot/metadata). +- [00-constitution.md](00-constitution.md) — [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai), [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant), [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש), [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים). diff --git a/docs/spec/X9-mcp-tool-contract.md b/docs/spec/X9-mcp-tool-contract.md new file mode 100644 index 0000000..ef799ae --- /dev/null +++ b/docs/spec/X9-mcp-tool-contract.md @@ -0,0 +1,101 @@ +# X9 — חוזה כלי-ה-MCP (Agent MCP Tool Contract) + +קובץ-תחום זה כפוף ל-[חוקת המערכת](00-constitution.md) והוא ה-deep-dive על **משטח כלי-ה-MCP** — +71 הכלים ש-[mcp-server](../../mcp-server/) חושף לסוכני Paperclip (CEO/analyst/researcher/writer/qa/…). +עד כה הספ תיאר *מה הסוכנים עושים* ([X4-agents.md](X4-agents.md)) אך לא **חוזה-הכלים** עצמו: envelope, +שמות, idempotency, סימטריית extract/get, ומפת-הרשאות. הקובץ מגדיר את הכללים; הממצאים → [gap-audit.md](gap-audit.md). + +> **מודלי-סמכות מעורבים.** TOOL1/TOOL2/TOOL3/TOOL5 הם **הנדסיים** (עיצוב-API/כלים — ≥3 מקורות). +> TOOL4 ו-TOOL6 הם **פרויקטלי-תפעוליים**, הנקשרים ל-[G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) +> ו-[G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant). + +--- + +## 1. אינוונטר (71 כלים, [server.py](../../mcp-server/src/legal_mcp/server.py)) + +| דומיין | כלים (מייצג) | +|--------|--------------| +| ניהול-תיק | case_create/list/get/update/delete, case_get_final_text | +| מסמכים | document_upload, document_upload_training, document_list/get_text/update, extract_references | +| טענות+טיעונים | extract_claims, get_claims, aggregate_claims_to_arguments, get_legal_arguments | +| **חיפוש (6 — חופפים)** | search_decisions, search_case_documents, find_similar_cases, search_internal_decisions, search_precedent_library, precedent_search_library | +| **כתיבת-בלוק (6 — חופפים)** | draft_section, get_block_context, write_block, write_all_blocks, write_interim_draft, save_block_content | +| ייצוא/QA | export_docx, export_interim_draft, validate_decision, revise_draft, list_bookmarks, apply_user_edit | +| פסיקה (3 תת-מערכות) | case-attached (precedent_attach/list/remove/search_library) · library (precedent_library_*) · internal (internal_decision_*) | +| הלכות | halacha_review, halachot_pending, precedent_extract_halachot/metadata, precedent_process_pending | +| ציטוטים | extract_internal_citations, list_internal_citations, list_incoming_citations | +| missing-precedents | missing_precedent_create/list/close | +| workflow/feedback | workflow_status, get_metrics, processing_status, set_outcome, brainstorm_directions, approve_direction, ingest_final_version, record/list_chair_feedback | +| appraiser/style | extract_appraiser_facts, style_corpus_enrich, style_corpus_pending_enrichment | + +--- + +## 2. Invariants של התחום + +### INV-TOOL1: envelope-תשובה עקבי לכל הכלים +**כלל:** כל כלי מחזיר **מבנה אחיד** (למשל `{status, data, message}`) — לא string-לפעמים-JSON-לפעמים-`{error}`. +שגיאה מובחנת ממצב-ריק ממצב-הצלחה באופן עקבי. מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים); +מקביל ל-[X6 INV-UI3](X6-ui-api-contract.md). **הנדסי.** +**מקורות:** Anthropic — *MCP / tool result conventions* (https://modelcontextprotocol.io/) · +JSON-RPC 2.0 (result/error envelope) (https://www.jsonrpc.org/specification) · RFC 9457 (Problem Details) | סטטוס: verified +**אכיפה:** wrapper-תשובה משותף בכל הכלים. **כיום אין** — מעורב. +**הפרה ידועה:** `search_*` מחזיר `"לא נמצאו תוצאות."` או JSON; חלק `{error}`, חלק raise ([gap-audit GAP-48](gap-audit.md)). + +### INV-TOOL2: שמות עקביים + חיפוש לפי-קורפוס +**כלל:** שמות-הכלים עוקבים אחר convention אחיד, ושם משקף התנהגות. כלי-חיפוש מובחנים **לפי הקורפוס** +(style / internal / external / case-attached), לא ב-6 שמות חופפים; כלי-כתיבת-בלוק אינם חופפים (context מול write). +מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) ("סימטריה", [§6](00-constitution.md#6-כללי-הנדסה-מונעים-הישנות)). **הנדסי.** +**מקורות:** Anthropic — *Writing effective tools / clear names* (https://www.anthropic.com/engineering/writing-tools-for-agents) · +Google *API Design Guide* (naming) (https://cloud.google.com/apis/design/naming_convention) · +Zalando *RESTful API Guidelines* | סטטוס: verified +**אכיפה:** איחוד/מיזוג כלי-חיפוש + כלי-בלוק; rename של שמות-מטעים. **כיום אין.** +**הפרה ידועה:** `precedent_search_library` מחפש למעשה quotes מצורפים-לתיק (שם מטעה); 6 כלי-חיפוש + 6 כלי-בלוק חופפים ([gap-audit GAP-49/50](gap-audit.md)). + +### INV-TOOL3: idempotency בכל כלי-מוטציה +**כלל:** כלי שמשנה-מצב הוא **idempotent על מפתח דטרמיניסטי** — קריאה חוזרת אינה יוצרת כפילות. מופע של +[G3](00-constitution.md#inv-g3-ingest-אחיד-ו-idempotent). **הנדסי.** +**מקורות:** Stripe — *Idempotent requests* (https://docs.stripe.com/api/idempotent_requests) · +Kleppmann *DDIA* (idempotence) · IETF — *Idempotency-Key header* draft (https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/) | סטטוס: verified +**אכיפה:** upsert/ON CONFLICT בכלי-מוטציה. **כיום חלקי** — `missing_precedent_create`/`precedent_link_cases`/`extract_internal_citations` idempotent; `case_create`/`document_upload`/`precedent_attach` לא. +**הפרה ידועה:** [gap-audit GAP-52](gap-audit.md). + +### INV-TOOL4: סימטריית extract/get + persistence +**כלל:** לכל כלי-חילוץ שכותב ל-DB יש **כלי-קריאה (get) מקביל**, והפלט **נשמר durably** (לא מוחזר-ונאבד). +מופע של [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים) (מקור-אמת נגיש). **פרויקטלי-תפעולי.** +**מקור-סמכות:** דפוס `extract_claims`↔`get_claims`, `aggregate`↔`get_legal_arguments` ב-[server.py](../../mcp-server/src/legal_mcp/server.py). +**אכיפה:** לכל extract — get מקביל. **כיום מופר:** `extract_appraiser_facts` כותב, **אין `get_appraiser_facts`** → חילוץ-חוזר יקר ולא-דטרמיניסטי. +**הפרה ידועה:** [gap-audit GAP-44](gap-audit.md); תור-חילוץ סמוי ([gap-audit GAP-45](gap-audit.md)). + +### INV-TOOL5: limit-caps על כל כלי-רשימה/חיפוש +**כלל:** לכל כלי שמחזיר רשימה יש **תקרת-limit נאכפת** (הגנה מפני עומס/DoS); pagination היכן שרלוונטי. **הנדסי.** +**מקורות:** OWASP API Security Top 10 — *API4:2023 Unrestricted Resource Consumption* (https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/) · +Microsoft *REST API Guidelines* (pagination) · Stripe API (limit caps) | סטטוס: verified +**אכיפה:** clamp ל-max בכל כלי-רשימה. **כיום אין** — `precedent_library_list`/`search_*`/`missing_precedent_list` ללא תקרה; `list_chair_feedback` ללא limit כלל ([gap-audit GAP-53](gap-audit.md)). +**הפרה ידועה:** [gap-audit GAP-53](gap-audit.md). + +### INV-TOOL6: שלמות-הרשאות — כל כלי שהוראות-הסוכן דורשות מוענק +**כלל:** מפת-ההרשאות (אילו כלים מוענקים לכל סוכן) **תואמת** את מה שהוראות-הסוכן מצריכות — לא חסר ולא עודף. +מופע של [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant) (שערים מוגדרים); מפורט ב-[X4-agents.md](X4-agents.md). **פרויקטלי-תפעולי.** +**מקור-סמכות:** frontmatter `tools:` ב-[.claude/agents/](../../.claude/agents/) מול הוראות-הסוכן. +**אכיפה:** בדיקת-עקביות tools↔instructions (יעד FU-13). +**הפרה ידועה:** legal-analyst חסר `aggregate_claims_to_arguments`/`extract_references`/`extract_internal_citations`; researcher חסר טריגרי-חילוץ ([gap-audit GAP-46](gap-audit.md)). + +--- + +## 3. הערות-עיצוב +- **set_outcome** — אי-התאמת enum: `block_writer.py:442` משתמש ב-`rejected/accepted/partial`, בעוד + `lessons.py:11` מגדיר `rejection/partial_acceptance/full_acceptance/betterment_levy`. שתי אוצרות-מילים → + SSoT יחיד נדרש ([gap-audit GAP-51](gap-audit.md); תואם [X6 INV-UI1](X6-ui-api-contract.md) על enum-סטטוס). +- **3 מסלולי-קליטת-פסיקה** (library / internal / training) עם ולידציה א-סימטרית — נקשר ל-[01-ingest.md](01-ingest.md) / GAP-01/05. + +הממצאים המלאים + התיקון → **FU-14** ([gap-audit.md](gap-audit.md)). + +--- + +## 4. הפניות-אחיות +- [X4-agents.md](X4-agents.md) — מפת-הסוכנים + ההרשאות (INV-TOOL6). +- [X8-field-provenance.md](X8-field-provenance.md) — כלי-החילוץ ומה שהם שומרים. +- [X6-ui-api-contract.md](X6-ui-api-contract.md) — envelope מקביל בצד-ה-API. +- [01-ingest.md](01-ingest.md), [03-retrieval.md](03-retrieval.md) — מסלולי-קליטה/חיפוש שהכלים עוטפים. +- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים), [G3](00-constitution.md#inv-g3-ingest-אחיד-ו-idempotent), [G10](00-constitution.md#inv-g10-המערכת-מסייעת--שערים-אנושיים-הם-invariant). +- [mcp-server/src/legal_mcp/server.py](../../mcp-server/src/legal_mcp/server.py), [tools/](../../mcp-server/src/legal_mcp/tools/). diff --git a/docs/spec/gap-audit.md b/docs/spec/gap-audit.md index 6537de4..2cfe306 100644 --- a/docs/spec/gap-audit.md +++ b/docs/spec/gap-audit.md @@ -17,6 +17,10 @@ ## 23 הממצאים +> **סטטוס מחזור-1 (עודכן 31.5.2026):** כל 23 הממצאים **✅ נסגרו** — FU-1..FU-8b מוזגו ל-main +> (PRs #11–#23: FU-1/2a, FU-2b #15, FU-2c #17, FU-3, FU-4, FU-5 #18, FU-6, FU-7 #13, FU-8a #16, FU-8b #23). +> 122 בדיקות עוברות. הטבלה נשמרת כתיעוד-מקור; פירוט-ה-FU והסטטוס בסעיף "יחידות-תיקון". + | ID | כותרת | invariant מופר | severity | קבצים מושפעים (file:line) | תיקון מוצע | |----|-------|----------------|----------|---------------------------|------------| | GAP-01 | שני מסלולי ingest מקבילים שמתפצלים | INV-ING1, G2 | High | `precedent_library.py:88`, `internal_decisions.py:73` | מסלול-קליטה קנוני יחיד; ישויות-אחיות חולקות פייפליין | @@ -45,12 +49,66 @@ --- +## ממצאי מחזור-2 (8 משטחי-האפליקציה מחוץ לצינור-הליבה) — GAP-24..62 + +> הופקו בסקירת-קוד word-for-word (30–31.5.2026) של 8 המשטחים: גבול-Paperclip, web-ui, +> מילוי-שדות, אחסון-ניתוחים, כלי-MCP (71), סוכנים+skills, deploy/env. ממצאי-ה-UI ברמת-הדף +> מפורטים ב-[ui-audit.md](ui-audit.md). ה-invariants ב-[X6](X6-ui-api-contract.md)–[X10](X10-deploy-env-secrets.md). +> **כל מחזור-2 פתוח** (אומת 31.5.2026: creds plaintext קיימים, 2 לקוחות קיימים, אין get_appraiser_facts, analyst חסר 3 כלים). + +| ID | כותרת | invariant מופר | severity | קבצים מושפעים (file:line) | תיקון מוצע | +|----|-------|----------------|----------|---------------------------|------------| +| GAP-24 | שני לקוחות Paperclip מקבילים (api מול client legacy) | INV-INT4, G2 | High | `web/paperclip_api.py`, `web/paperclip_client.py` | לקוח-API קנוני יחיד | +| GAP-25 | גישת-DB ישירה ל-Paperclip (INSERT projects/issues/plugin_state) עוקפת API+audit | INV-INT4, G2, G9 | High | `web/paperclip_client.py` | להעביר הכל ל-API; להסיר מסלול-DB | +| GAP-26 | company/agent IDs קשיחים — **סותר X3 §1א** | INV-INT5, G2 | High | `web/paperclip_client.py:36-62`, `web/app.py:3976`, plugin `worker.ts` | מיפוי מ-config/env | +| GAP-27 | `company_id` נגזר בשתי דרכים (prefix מול fallback-map) | INV-INT6, G2 | Medium | `web/app.py` (prefix), `web/paperclip_client.py` (`_FALLBACK_APPEAL_TYPE_TO_COMPANY`) | פונקציית-גזירה יחידה | +| GAP-28 | webhooks fire-and-forget בולעים שגיאות, ללא idempotency | INV-INT7, G9, §6 | Medium | `web/paperclip_api.py:87-205` | event-id+dedup+רישום-כישלון | +| GAP-29 | חוזה-אירוע webhook לא-מתוקען (eventType חופשי, default שקט) | INV-INT8, G2 | Medium | `web/paperclip_api.py:87+`, plugin `onWebhook` | enum-אירוע מגורס | +| GAP-30 | ~60% endpoints ללא Pydantic → `unknown` → טיפוסים ידניים סוטים | INV-UI1/UI2, G2/G4 | High | `web/app.py` (רוב), `web-ui/src/lib/api/cases.ts:1-9` | response models + `api:types` | +| GAP-31 | `PracticeArea`/enum-סטטוס משוכפלים פרונט (3 מקומות, ערכים שונים) | INV-UI1, G2 | High | `web-ui/src/lib/practice-area.ts:12`, `lib/api/precedent-library.ts:26`, `components/precedents/practice-area.ts` | SSoT יחיד (ui-audit UI-A1/B1) | +| GAP-32 | אין envelope עקבי; שגיאות נבלעות ב-UI | INV-UI3/UI4, §6 | Medium | `web/app.py` (search ועוד), דפי-UI | envelope אחיד + error-card | +| GAP-33 | fallback SSE מסתיר כישלון; cache-TTL לא-תואם (5ש'↔300ש') | INV-UI5 | Low | `web-ui/src/lib/api/documents.ts:226-232` | terminal-state מפורש | +| GAP-34 | URLs קשיחים ב-UI/בק | INV-UI3/ENV3 | Low | `web-ui/.../app-shell.tsx:70`, `web/app.py:110` | env | +| GAP-35 | מקור-מילוי-שדות לא-מוצהר — מפוזר על 4 שירותים | INV-FP1, G9 | High | `precedent_metadata_extractor.py`, `halacha_extractor.py`, `ingest.py`, `db.py` (recompute_searchable) | טבלת-provenance SSoT (X8 §2) | +| GAP-36 | אין שקיפות-UI למה מולא ע"י Opus מול ידני | INV-UI6/FP1, G9 | Medium | `web-ui/src/app/precedents/[id]/page.tsx:160-185` | חיווי מקור-מילוי | +| GAP-37 | placeholder `"(טרם חולץ)"` כמחרוזת-קסם לא-מתועדת | INV-FP2 | Low | `internal_decisions.py`, `precedent_metadata_extractor.py` | constant מתועד | +| GAP-38 | שתי עמודות-סטטוס-חילוץ ב-case_law | INV-DM1, G2 | Medium | `db.py:603-606` | סטטוס יחיד / extraction-jobs | +| GAP-39 | `legal_arguments` ללא שער-אישור (בניגוד ל-halachot) | INV-DM5, G10 | High | `db.py:845-872` | `review_status` ל-legal_arguments | +| GAP-40 | `legal_arguments.cited_precedents TEXT[]` ללא FK → הזיות-LLM נבלעות | INV-DM6, G9, §6 | Medium | `db.py:858`, `argument_aggregator.py` | FK + דיווח-כישלון-קישור | +| GAP-41 | `appraiser_facts`↔`claims` התנגשות; `appraiser_side` default '' מעורפל | INV-DM6 | Medium | `db.py:549-576` | CHECK + הבחנה document↔case | +| GAP-42 | 20+ enums כ-TEXT חופשי; אין embedding-provenance | INV-DM6/DM4, G4 | Medium | `db.py` (source_type, rule_type, status…) | CHECK-enums + עמודת-model | +| GAP-43 | `case_precedents`↔`case_law` טבלאות-פסיקה מקבילות legacy | INV-G2 | Low | `db.py` | איחוד/סימון-deprecated | +| GAP-44 | אסימטריית extract/get — אין `get_appraiser_facts` (חילוץ-חוזר יקר) | INV-TOOL4, G2 | High | `mcp-server/.../drafting.py`, `server.py:563` | להוסיף `get_appraiser_facts` | +| GAP-45 | תור-חילוץ סמוי (pending-initial מול pending-review); אין extraction-job table | INV-TOOL4/FP5, G10 | Medium | `precedent_library.py`, `ingest.py` | `*_extraction_status` tool + טבלת-jobs | +| GAP-46 | הרשאות-סוכן לא-מתועדות (analyst/researcher חסרי כלים) | INV-AG3/TOOL6 | High | `.claude/agents/legal-analyst.md`, `legal-researcher.md` | יישור tools↔instructions | +| GAP-47 | `draft_section` ללא provenance (chunk→document/page); הנחיות-יו"ר ב-md ולא DB | INV-TOOL4, G9 | Medium | `mcp-server/.../drafting.py` | provenance בפלט + DB ל-directions | +| GAP-48 | envelope-תשובה לא-עקבי (71 כלים: string/JSON/{error}) | INV-TOOL1, G2 | Medium | `mcp-server/.../server.py`, tools/ | wrapper `{status,data,message}` | +| GAP-49 | 6 כלי-חיפוש חופפים + `precedent_search_library` שם-מטעה | INV-TOOL2, G2 | Medium | `server.py` (search_*), `precedents.py:81` | מיזוג + rename לפי-קורפוס | +| GAP-50 | 6 כלי-כתיבת-בלוק חופפים (draft_section/get_block_context/write_*/save_*) | INV-TOOL2, G2 | Medium | `server.py:500-616` | מיזוג context↔write | +| GAP-51 | `set_outcome` enum-mismatch (3≠4); אוצרות-מילים סותרות | INV-TOOL1/UI1 | Medium | `block_writer.py:442` מול `lessons.py:11`, `workflow.py:145` | SSoT יחיד ל-outcome | +| GAP-52 | רוב הכלים לא-idempotent (case_create/document_upload/precedent_attach) | INV-TOOL3, G3 | Medium | `server.py`, tools/ | upsert/ON CONFLICT | +| GAP-53 | אין limit-caps (precedent_library_list/search_*/list_chair_feedback) | INV-TOOL5 | Low | tools/ | clamp ל-max | +| GAP-54 | 3 מסלולי-קליטת-פסיקה ולידציה א-סימטרית; citation-guard לא-מתועד | INV-ING1, G2 | Medium | `precedent_library.py`, `internal_decisions.py` | איחוד (תואם GAP-01/05) | +| GAP-55 | Infisical dead-code; מקור-config לא-מתועד (Coolify-only) | INV-ENV2, G2 | Medium | `mcp-server/.../config.py` | לתעד Coolify SSoT / לבודד Infisical | +| GAP-56 | UUIDs קשיחים (company/agent) — תואם GAP-26 | INV-ENV3/INT5 | High | `web/paperclip_client.py:36-62`, `web/app.py:3976` | config-driven | +| GAP-57 | creds plaintext בברירת-מחדל (`paperclip:paperclip`) | INV-ENV4, G9, §6 | High | `web/paperclip_client.py:21`, `web/app.py:3789,3964` | default ריק + fail-loud | +| GAP-58 | `GITEA_ACCESS_TOKEN`↔`GITEA_TOKEN` שני שמות; קטלוג חלקי | INV-ENV1 | Low | `web/gitea_client.py:22`, `git_sync.py:30`, `tools/cases.py:28` | שם קנוני יחיד + קטלוג | +| GAP-59 | chat-URL docs↔reality (`10.0.1.1` מול `host.docker.internal`) | INV-ENV3 | Medium | `web/chat_proxy.py:49`, `chat_service/server.py` | יישור env + תיעוד | +| GAP-60 | 13/40+ env vars ב-drift-catalog; 8+ סודות בלתי-מנוטרים | INV-ENV5/ENV1 | Medium | `web/mcp_env_catalog.py` | קטלוג מקיף | +| GAP-61 | URLs + `/home/chaim` קשיחים | INV-ENV3 | Low | `web/paperclip_client.py:31`, app.py | env/config | +| GAP-62 | start.sh לא-נכשל-על-uvicorn; deploy-curl fire-and-forget | INV-ENV2/§6 | Low | `start.sh`, `.gitea/workflows/deploy.yaml` | health-gate + אימות-deploy | + +--- + ## יחידות-תיקון מוצעות (Proposed Fix-Units) 23 הממצאים מקובצים ל-8 יחידות-עבודה קוהרנטיות. הקיבוץ נגזר מהעיקרון שרבים מהממצאים נפתרים יחד (כל פערי ה-ingest-asymmetry → יחידה אחת). זהו זרע למשימות TaskMaster ולתת-פרויקט 3 (שכבת-שלמות). +> **✅ מחזור-1 הושלם (31.5.2026):** FU-1..FU-8b כולם מוזגו ל-main. מחזור-2 (FU-9..15, להלן) +> נגזר מ-GAP-24..62 ו**פתוח**. + ### 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) @@ -110,6 +168,37 @@ - **תלויות:** ה-spec גמור (GAP-23 דורש קבצי-ספ יציבים לחבר לסוכנים) - **סוג:** pure-code + **chair-decision** — GAP-23 (חיבור ספ לסוכני-Paperclip) הוא prerequisite לתת-פרויקט 5 ומשנה התנהגות-סוכן בייצור +- **סטטוס:** ✅ FU-8a (GAP-21/22, PR #16) + FU-8b (GAP-23, PR #23) מוזגו. + +> **— מחזור-2 (FU-9..15): 8 משטחי-האפליקציה מחוץ לצינור-הליבה. כולם פתוחים. —** + +### FU-9 — לקוח-Paperclip קנוני +- **מכסה:** GAP-24..29 · **invariants:** INV-INT4–INT8 · **effort:** L · **תלויות:** [X7](X7-paperclip-client-params.md) יציב +- **סוג:** code — איחוד 2 הלקוחות, הסרת מסלול-DB, IDs מ-config, company_id יחיד, webhook idempotency+enum + +### FU-10 — חוזה UI↔API + design-system SSoT +- **מכסה:** GAP-30..34 + [ui-audit](ui-audit.md) (UI-A1..D6) · **invariants:** INV-UI1–UI6 · **effort:** L · **תלויות:** — +- **סוג:** code — Pydantic models+`api:types`, SSoT ל-enums/תוויות/tones, helpers משותפים, ניקוי redundancy + +### FU-11 — מילוי-שדות מוצהר + שקיפות-UI +- **מכסה:** GAP-35..37 · **invariants:** INV-FP1–FP5, UI6 · **effort:** M · **תלויות:** — +- **סוג:** code — טבלת-provenance SSoT, formalize placeholder, חיווי "מולא-ע"י-Opus" + searchable + pending ב-UI + +### FU-12 — חיזוק אחסון-הניתוחים +- **מכסה:** GAP-38..43 · **invariants:** INV-DM4–DM6 · **effort:** M · **תלויות:** FU-1 +- **סוג:** code + data-migration קל — provenance, שער-אישור ל-legal_arguments, CHECK-enums, FK, איחוד case_precedents + +### FU-13 — סוכנים + skills +- **מכסה:** GAP-46 (מרחיב GAP-23) · **invariants:** INV-AG3, INV-TOOL6 · **effort:** S · **תלויות:** ה-spec יציב +- **סוג:** code/docs — שלמות-הרשאות (tools↔instructions), DRY-boilerplate, dedup-skills + +### FU-14 — חוזה כלי-ה-MCP +- **מכסה:** GAP-44,45,47..54 · **invariants:** INV-TOOL1–TOOL5 · **effort:** L · **תלויות:** FU-1 +- **סוג:** code — envelope אחיד, מיזוג חיפוש/בלוקים, idempotency, limit-caps, get-symmetry, set_outcome SSoT + +### FU-15 — deploy/env/secrets +- **מכסה:** GAP-55..62 · **invariants:** INV-ENV1–ENV5 · **effort:** M · **תלויות:** — +- **סוג:** code/config + **chair-decision** (rotation סודות) — env-catalog SSoT, מקור-config יחיד, de-hardcode, drift מלא, start.sh עמיד --- @@ -122,4 +211,10 @@ 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 אחרי ייצוב-הספ. +FU-7 אחרי FU-1; FU-5 אחרי FU-2; FU-8 אחרי ייצוב-הספ. **(מחזור-1 ✅ הושלם.)** + +**מחזור-2 (FU-9..15) — 8 משטחי-האפליקציה:** FU-10 (UI+design-system) ו-FU-15 (deploy/env) עצמאיים — +ניתן במקביל. FU-9 (לקוח-Paperclip) אחרי [X7](X7-paperclip-client-params.md). FU-12 (אחסון) ו-FU-14 (כלי-MCP) +אחרי FU-1. FU-11 (מילוי-שדות) עצמאי. FU-13 (סוכנים+skills) אחרי ייצוב-הספ. +**סיווג:** pure-code — FU-9/10/11/13/14; +data-migration קל — FU-12; +chair-decision — FU-15 (rotation סודות). +priority בפועל — של היו"ר. diff --git a/docs/spec/ui-audit.md b/docs/spec/ui-audit.md new file mode 100644 index 0000000..7ddb870 --- /dev/null +++ b/docs/spec/ui-audit.md @@ -0,0 +1,72 @@ +# UI-Audit — ביקורת דף-אחר-דף של ה-web-ui + +מסמך זה הוא **מפת-הממצאים של ה-frontend** (web-ui), מקביל ל-[gap-audit.md](gap-audit.md) אך ברמת-הדף/הרכיב. +הוא תוצר סריקה word-for-word של 13 הדפים (5 cases-flow + 5 knowledge + 3 admin) + השכבה המשותפת. +כל ממצא נושא: `invariant מופר` (מ-[X6](X6-ui-api-contract.md)/[X8](X8-field-provenance.md)) · `severity` · +`file:line` · `תיקון`. severity = הערכה הנדסית; priority = היו"ר. + +**איך הופק:** סקירת 13 הדפים + `src/lib/api/*` + `src/components/*`, מאומת מול הקוד. התיקון מקובץ ל-**FU-10**. + +> **דפים שנסרקו:** dashboard, cases/new, cases/[caseNumber], cases/[caseNumber]/compose, archive · +> precedents, precedents/[id], training, methodology, missing-precedents · settings, skills, diagnostics. +> **נווט מלא:** כל 13 הדפים נגישים מ-[app-shell.tsx](../../web-ui/src/components/app-shell.tsx) — אין דף-יתום. + +--- + +## 1. מוגדר-לא-נכון (Wrong Definitions) + +| ID | כותרת | invariant | severity | file:line | תיקון | +|----|-------|-----------|----------|-----------|-------| +| UI-A1 | `PracticeArea` מוגדר ב-**3 מקומות עם ערכים שונים** — [lib/practice-area.ts:12](../../web-ui/src/lib/practice-area.ts) (`appeals_committee/national_insurance/labor_law` — שאריות מפרויקט אחר!), [lib/api/precedent-library.ts:26](../../web-ui/src/lib/api/precedent-library.ts) (`rishuy_uvniya/...`), ו-[components/precedents/practice-area.ts](../../web-ui/src/components/precedents/practice-area.ts) | UI1, G2 | **CRITICAL** | 3 קבצים | SSoT יחיד; הסרת שאריות national_insurance/labor_law | +| UI-A2 | `key_quote` חסר מטיפוס `Precedent`; גישה דרך `as {key_quote?:string}` | UI1/UI2 | **CRITICAL** | [precedents/[id]/page.tsx:178](../../web-ui/src/app/precedents/%5Bid%5D/page.tsx), [precedent-edit-sheet.tsx:94](../../web-ui/src/components/precedents/precedent-edit-sheet.tsx) | הוספת `key_quote` לטיפוס/OpenAPI | +| UI-A3 | תווית לא-עקבית לאותו ערך: "פיצויים (197)" מול "פיצויים לפי ס' 197" | UI1 | High | [precedents/[id]/page.tsx:26-35](../../web-ui/src/app/precedents/%5Bid%5D/page.tsx) מול practice-area.ts | תווית מ-SSoT יחיד | +| UI-A4 | enum נצרך לא-נגזר-מטיפוס (zod ידחה subtype חדש) | UI1 | High | [schemas/case.ts:78-86](../../web-ui/src/lib/schemas/case.ts) | zod נגזר מ-PracticeArea/AppealSubtype | +| UI-A5 | `expectedOutcomes`/`set_outcome` — אוצר-מילים לא-תואם בק (`rejected/accepted/partial` מול `rejection/.../betterment_levy`) | UI1, G2 | High | [schemas/case.ts:35-41](../../web-ui/src/lib/schemas/case.ts); בק `block_writer.py:442`/`lessons.py:11` | SSoT יחיד ל-enum-תוצאה | + +## 2. כפילות (Duplication) + +| ID | כותרת | invariant | severity | file:line | תיקון | +|----|-------|-----------|----------|-----------|-------| +| UI-B1 | `CaseStatus` + `STATUS_LABELS` + `STATUS_TONE` ב-3 מקומות | UI1, G2 | **CRITICAL** | [cases.ts:16-33](../../web-ui/src/lib/api/cases.ts), [status-badge.tsx:11-29,77-95](../../web-ui/src/components/cases/status-badge.tsx), [status-changer.tsx:18-24](../../web-ui/src/components/cases/status-changer.tsx) | enum+labels+tones מ-SSoT, ייבוא | +| UI-B2 | `STATUS_LABELS` של מסמכים משוכפל (ולא-שלם) | UI1 | High | [upload-sheet.tsx:39-46](../../web-ui/src/components/documents/upload-sheet.tsx), [documents-panel.tsx:39-46](../../web-ui/src/components/cases/documents-panel.tsx) | ל-`lib/doc-types.ts` | +| UI-B3 | פירמוט-תאריך משוכפל ×5 | UI/§6 | Medium | archive.tsx, case-header.tsx, documents-panel.tsx (+2) | `lib/format.ts` משותף | +| UI-B4 | תוויות practice-area/source-type משוכפלות | UI1 | High | [precedents/[id]/page.tsx:26-35](../../web-ui/src/app/precedents/%5Bid%5D/page.tsx) | ייבוא מ-practice-area.ts | +| UI-B5 | boilerplate העלאת-קבצים (FormData+fetch) ×4 | §6/G2 | Medium | documents.ts, training.ts, exports.ts, missing-precedents.ts | `uploadMultipart()` ב-client.ts | +| UI-B6 | כרטיס-שגיאה משוכפל ×3 | UI4 | Medium | detail/library/missing pages | `` משותף | + +## 3. מיותר / מת (Redundancy / Dead) + +| ID | כותרת | invariant | severity | file:line | תיקון | +|----|-------|-----------|----------|-----------|-------| +| UI-C1 | 3 דפי-פסיקה חופפים (/precedents, /training, /missing-precedents) — גבולות מטושטשים | G2 | Medium | 3 דפים | הגדרת אחריות; שקילת איחוד | +| UI-C2 | כפתור "חלץ מטא-דאטה" שלא מרענן, מפנה ל-CLI ידני | UI5/FP5 | Medium | [precedent-edit-sheet.tsx:130](../../web-ui/src/components/precedents/precedent-edit-sheet.tsx) | auto-refresh/poll על תור-החילוץ | +| UI-C3 | `useCase` refetch כל 5ש' גם במנוחה/בעריכה | UI5 | Low | [cases.ts:150-152](../../web-ui/src/lib/api/cases.ts) | interval מותנה-סטטוס | +| UI-C4 | magic-numbers (intervals) מפוזרים ב-18 מודולים | UI5/§6 | Low | כל `lib/api/*` | `lib/api/query-config.ts` | + +## 4. אי-עקביות + הפרת-כללים (Inconsistency / Rule-Violations) + +| ID | כותרת | invariant | severity | file:line | תיקון | +|----|-------|-----------|----------|-----------|-------| +| UI-D1 | ~60% endpoints `unknown` → טיפוסים ידניים-סוטים | UI1/UI2 | **CRITICAL** | [cases.ts:1-9](../../web-ui/src/lib/api/cases.ts) (מתועד מפורשות) + בק | Pydantic models + `api:types` | +| UI-D2 | שדות-Opus מוצגים ללא חיווי "חולץ-אוטומטית"; היו"ר לא יודע מה לאמת | UI6/FP1 | High | [precedents/[id]/page.tsx:160-185](../../web-ui/src/app/precedents/%5Bid%5D/page.tsx) | badge "מולא-ע"י-Opus" | +| UI-D3 | אין חיווי `searchable`; הלכות `pending_review` לא מובלטות בדף-הפרט | UI6/FP3 | High | precedents/[id]/page.tsx | חיווי searchable + אזהרת-pending | +| UI-D4 | fallback SSE מסתיר כישלון כ-"completed"; TTL 5ש'↔300ש' | UI4/UI5 | Medium | [documents.ts:226-232](../../web-ui/src/lib/api/documents.ts) | terminal-state מפורש | +| UI-D5 | query-keys לא-עקביים (חלק `.all`, חלק לא; חלק exported, חלק לא) | §6 | Low | agents.ts, feedback.ts (+) | convention אחיד | +| UI-D6 | URLs קשיחים (`PAPERCLIP_BASE`, coolify, frontend) | UI3/ENV3 | Low | [app-shell.tsx:70](../../web-ui/src/components/app-shell.tsx) | env (ראה [X10](X10-deploy-env-secrets.md)) | + +--- + +## 5. סיכום ל-FU-10 +- **SSoT ל-enums/תוויות/tones** (UI-A1..A5, UI-B1/B2/B4) — תיקון-השורש של רוב הממצאים. +- **Pydantic models + OpenAPI=SSoT** (UI-D1) — מבטל את הטיפוסים-הידניים. +- **helpers משותפים** (UI-B3/B5/B6, UI-C4) — תאריך, upload, error-card, query-config. +- **שקיפות-מקור-מילוי** (UI-D2/D3) — נגזר מ-[X8](X8-field-provenance.md)/[X6 INV-UI6](X6-ui-api-contract.md). +- **ניקוי redundancy** (UI-C1..C3). + +--- + +## 6. הפניות-אחיות +- [X6-ui-api-contract.md](X6-ui-api-contract.md) — ה-invariants (UI1–UI6) שממצאים אלו מפרים. +- [X8-field-provenance.md](X8-field-provenance.md) — מקור-מילוי (בסיס ל-UI-D2/D3). +- [gap-audit.md](gap-audit.md) — GAP-30..34 (התקבילים ברמת-הארכיטקטורה) + FU-10. +- [00-constitution.md](00-constitution.md) — [G2](00-constitution.md#inv-g2-מקור-אמת-יחיד--אין-מסלולים-מקבילים-מתפצלים), [G4](00-constitution.md#inv-g4-חוזה-שלמות-לפני-שמיש--ניתן-לחיפוש), [G9](00-constitution.md#inv-g9-עקיבוּת-מקור--audit-trail-ל-ai). -- 2.49.1