From 40c1111e9be26a901a7526887f4cc058a66c109f Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 6 Jun 2026 15:58:39 +0000 Subject: [PATCH] =?UTF-8?q?feat(mcp):=20FU-14=20GAP-47=20(=D7=97=D7=9C?= =?UTF-8?q?=D7=A7=20provenance)=20=E2=80=94=20draft=5Fsection=20=D7=9E?= =?UTF-8?q?=D7=97=D7=96=D7=99=D7=A8=20document=5Fid+page+score?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ה-provenance (document_id, page_number, score) כבר נשלף ב-search_similar אך נזרק בבניית פלט draft_section. כעת מוחזר לכל קטע ב-case_documents/precedents, כך שהכותב יכול לעקוב אחורה אל מסמך-המקור והעמוד ולצטטם, ולא לסמוך על תוכן חסר-מקור. תוספתי בלבד — אין צרכן שמפרסר את מפתחות-הפלט, תואם-לאחור. נותר ב-GAP-47: העברת הנחיות-יו"ר מ-analysis-and-research.md ל-DB (get_chair_directions) — שינוי-מסלול גדול יותר, לפרוסה נפרדת. Invariants: מקיים INV-TOOL4 (מקור-אמת נגיש) + G9 (provenance). לא נוגע ב-G2/G1. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/spec/X9-mcp-tool-contract.md | 2 +- docs/spec/gap-audit.md | 3 ++- mcp-server/src/legal_mcp/tools/drafting.py | 13 +++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/spec/X9-mcp-tool-contract.md b/docs/spec/X9-mcp-tool-contract.md index 2ff33ef..82f0ad1 100644 --- a/docs/spec/X9-mcp-tool-contract.md +++ b/docs/spec/X9-mcp-tool-contract.md @@ -63,7 +63,7 @@ Kleppmann *DDIA* (idempotence) · IETF — *Idempotency-Key header* draft (https **כלל:** לכל כלי-חילוץ שכותב ל-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 מקביל. **GAP-44 ✅ + GAP-45 ✅ נסגרו (2026-06-06):** נוסף `get_appraiser_facts` (קורא `list_appraiser_facts`+`detect_appraiser_conflicts`, ללא חילוץ-מחדש); נוסף `extraction_status` שחושף את עומק תור-החילוץ (metadata/halacha) + גיל הבקשה הוותיקה — read-only. +**אכיפה:** לכל extract — get מקביל. **GAP-44 ✅ + GAP-45 ✅ נסגרו (2026-06-06):** נוסף `get_appraiser_facts` (קורא `list_appraiser_facts`+`detect_appraiser_conflicts`, ללא חילוץ-מחדש); נוסף `extraction_status` שחושף את עומק תור-החילוץ (metadata/halacha) + גיל הבקשה הוותיקה — read-only. **GAP-47 (חלק provenance) ✅ נסגר (2026-06-06):** `draft_section` מחזיר `document_id`+`page`+`score` לכל קטע (provenance מ-`search_similar` שהיה נזרק) → מקור-אמת נגיש ובר-ציטוט (G9). נותר ב-GAP-47: הנחיות-יו"ר ל-DB (פרוסה נפרדת). **הפרה ידועה:** — ### INV-TOOL5: limit-caps על כל כלי-רשימה/חיפוש diff --git a/docs/spec/gap-audit.md b/docs/spec/gap-audit.md index 0ee78f0..4197ee7 100644 --- a/docs/spec/gap-audit.md +++ b/docs/spec/gap-audit.md @@ -200,7 +200,8 @@ - **סוג:** code — envelope אחיד, מיזוג חיפוש/בלוקים, idempotency, limit-caps, get-symmetry, set_outcome SSoT - **סטטוס חלקי (פרוסה 1, 2026-06-06):** ✅ **GAP-44** — נוסף `get_appraiser_facts` (ה-get המקביל ל-extract, INV-TOOL4); ✅ **GAP-53** — נוסף `_clamp_limit` (תקרה 200, INV-TOOL5) על ~13 כלי list/search + הוספת limit ל-`list_chair_feedback` (שהיה ללא תקרה). - **סטטוס חלקי (פרוסה 2, 2026-06-06):** ✅ **GAP-52** (INV-TOOL3 idempotency) — `case_create`/`precedent_attach`/`document_upload` מחזירים קיים במקום כפילות (בדיקת-מפתח ברמת-אפליקציה; document_upload לפי SHA-256 → מדלג OCR/embed כפול); ✅ **GAP-45** (INV-TOOL4 visibility) — נוסף `extraction_status` שחושף עומק תור-החילוץ (metadata/halacha) + גיל הבקשה הוותיקה. -- **סטטוס חלקי (פרוסה 3, 2026-06-06):** ✅ **GAP-51** (set_outcome SSoT, הכרעת-יו"ר "3 תוצאות + הוצאת betterment_levy") — קנוני `rejection/partial_acceptance/full_acceptance` ב-`lessons.VALID_OUTCOMES`; `OUTCOME_LABELS_HE` (עברית-ב-UI SSoT); `canonical_outcome()` ל-legacy; `betterment_levy`→`PRACTICE_AREA_OVERRIDES` (override לפי practice_area); block_writer/set_outcome/drafting/web-ui יושרו; נתונים נורמלו (9 שורות + גיבוי). נותר ב-FU-14: GAP-48 (envelope), GAP-49/50 (מיזוג+rename — שובר). +- **סטטוס חלקי (פרוסה 3, 2026-06-06):** ✅ **GAP-51** (set_outcome SSoT, הכרעת-יו"ר "3 תוצאות + הוצאת betterment_levy") — קנוני `rejection/partial_acceptance/full_acceptance` ב-`lessons.VALID_OUTCOMES`; `OUTCOME_LABELS_HE` (עברית-ב-UI SSoT); `canonical_outcome()` ל-legacy; `betterment_levy`→`PRACTICE_AREA_OVERRIDES` (override לפי practice_area); block_writer/set_outcome/drafting/web-ui יושרו; נתונים נורמלו (9 שורות + גיבוי). +- **סטטוס חלקי (פרוסה 4, 2026-06-06):** ✅ **GAP-47 (חלק provenance, INV-TOOL4/G9)** — `draft_section` חושף בפלט `document_id`+`page`+`score` לכל קטע ב-`case_documents`/`precedents` (ה-provenance כבר נשלף ב-`search_similar` ונזרק; כעת מוחזר), כך שהכותב יכול לעקוב אל המקור ולצטטו. תוספתי, לא-שובר. **נותר ב-GAP-47:** העברת הנחיות-יו"ר מ-`analysis-and-research.md` ל-DB (`get_chair_directions`) — שינוי-מסלול גדול יותר הנוגע ל-UI של דפנה ולזרימת-האנליסט; לפרוסה נפרדת. נותר ב-FU-14: GAP-48 (envelope), GAP-49/50 (מיזוג+rename — שובר), GAP-54 (איחוד קליטת-פסיקה). ### FU-15 — deploy/env/secrets - **מכסה:** GAP-55..62 · **invariants:** INV-ENV1–ENV5 · **effort:** M · **תלויות:** — diff --git a/mcp-server/src/legal_mcp/tools/drafting.py b/mcp-server/src/legal_mcp/tools/drafting.py index af4c7e5..7b68e3a 100644 --- a/mcp-server/src/legal_mcp/tools/drafting.py +++ b/mcp-server/src/legal_mcp/tools/drafting.py @@ -199,6 +199,10 @@ async def draft_section( case_number: מספר תיק הערר section: סוג הסעיף (facts, appellant_claims, respondent_claims, legal_analysis, conclusion, ruling) instructions: הנחיות נוספות לניסוח + + כל קטע ב-case_documents/precedents מלווה ב-provenance: document_id, page + (מספר עמוד במסמך-המקור, אם קיים) ו-score — כדי שניתן יהיה לעקוב אחורה + אל המקור ולצטטו, ולא לסמוך על התוכן ללא מקור. """ case = await db.get_case_by_number(case_number) if not case: @@ -245,10 +249,16 @@ async def draft_section( }, "section": section, "instructions": instructions, + # GAP-47 (INV-TOOL4/G9): surface provenance — document_id + page — + # so the writer can cite chunks back to their source (already fetched + # by search_similar, was previously discarded). "case_documents": [ { "document": c["document_title"], + "document_id": str(c["document_id"]), + "page": c.get("page_number"), "section_type": c["section_type"], + "score": round(c.get("score", 0.0), 4), "content": c["content"], } for c in case_chunks @@ -257,6 +267,9 @@ async def draft_section( { "case_number": c["case_number"], "document": c["document_title"], + "document_id": str(c["document_id"]), + "page": c.get("page_number"), + "score": round(c.get("score", 0.0), 4), "content": c["content"][:500], } for c in precedent_chunks[:3]