From ea8b48c6acf676c2f9672590f15244e19b474c32 Mon Sep 17 00:00:00 2001 From: Chaim Date: Sat, 6 Jun 2026 15:00:25 +0000 Subject: [PATCH] =?UTF-8?q?feat(mcp):=20FU-14=20GAP-45=20=E2=80=94=20extra?= =?UTF-8?q?ction=5Fstatus=20(=D7=97=D7=A9=D7=99=D7=A4=D7=AA=20=D7=AA=D7=95?= =?UTF-8?q?=D7=A8-=D7=94=D7=97=D7=99=D7=9C=D7=95=D7=A5=20=D7=94=D7=A1?= =?UTF-8?q?=D7=9E=D7=95=D7=99)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit INV-TOOL4 (visibility / persistence). תור בקשות-החילוץ (metadata/halacha) נשמר ב-case_law.{metadata,halacha}_extraction_requested_at ומרוקן ע"י precedent_process_pending — אבל לא היה כלי לראות את עומק-התור. נוסף: - db.extraction_queue_status() — count + גיל הבקשה הוותיקה לכל kind (read-only). - plib.extraction_status() — tool wrapper (envelope _ok/_err). - רישום extraction_status ב-server.py ליד precedent_process_pending. - precedent_process_pending קיבל _clamp_limit (עקביות עם GAP-53). תוספתי, read-only, אפס שבירה. עודכנו X9 (INV-TOOL4 ✅) ו-gap-audit (GAP-45 ✅). py_compile עבר על 3 קבצי הקוד. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/spec/X9-mcp-tool-contract.md | 4 +-- docs/spec/gap-audit.md | 2 +- mcp-server/src/legal_mcp/server.py | 8 +++++- mcp-server/src/legal_mcp/services/db.py | 25 +++++++++++++++++++ .../src/legal_mcp/tools/precedent_library.py | 13 ++++++++++ 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/docs/spec/X9-mcp-tool-contract.md b/docs/spec/X9-mcp-tool-contract.md index dfca590..f7134d4 100644 --- a/docs/spec/X9-mcp-tool-contract.md +++ b/docs/spec/X9-mcp-tool-contract.md @@ -63,8 +63,8 @@ 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 ✅ נסגר (2026-06-06):** נוסף `get_appraiser_facts` (קורא `list_appraiser_facts`+`detect_appraiser_conflicts`, ללא חילוץ-מחדש). נותר: תור-חילוץ סמוי (GAP-45). -**הפרה ידועה:** תור-חילוץ סמוי ([gap-audit GAP-45](gap-audit.md)). +**אכיפה:** לכל extract — get מקביל. **GAP-44 ✅ + GAP-45 ✅ נסגרו (2026-06-06):** נוסף `get_appraiser_facts` (קורא `list_appraiser_facts`+`detect_appraiser_conflicts`, ללא חילוץ-מחדש); נוסף `extraction_status` שחושף את עומק תור-החילוץ (metadata/halacha) + גיל הבקשה הוותיקה — read-only. +**הפרה ידועה:** — ### INV-TOOL5: limit-caps על כל כלי-רשימה/חיפוש **כלל:** לכל כלי שמחזיר רשימה יש **תקרת-limit נאכפת** (הגנה מפני עומס/DoS); pagination היכן שרלוונטי. **הנדסי.** diff --git a/docs/spec/gap-audit.md b/docs/spec/gap-audit.md index c85ffd1..62f6ed8 100644 --- a/docs/spec/gap-audit.md +++ b/docs/spec/gap-audit.md @@ -199,7 +199,7 @@ - **מכסה:** GAP-44,45,47..54 · **invariants:** INV-TOOL1–TOOL5 · **effort:** L · **תלויות:** FU-1 - **סוג:** 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 (status-tool), GAP-51 (set_outcome enum SSoT — דורש הכרעת-domain), GAP-48 (envelope), GAP-49/50 (מיזוג+rename — שובר). +- **סטטוס חלקי (פרוסה 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) + גיל הבקשה הוותיקה. נותר: GAP-51 (set_outcome enum SSoT — דורש הכרעת-domain), GAP-48 (envelope), GAP-49/50 (מיזוג+rename — שובר). ### FU-15 — deploy/env/secrets - **מכסה:** GAP-55..62 · **invariants:** INV-ENV1–ENV5 · **effort:** M · **תלויות:** — diff --git a/mcp-server/src/legal_mcp/server.py b/mcp-server/src/legal_mcp/server.py index 3852fe4..61ee9e0 100644 --- a/mcp-server/src/legal_mcp/server.py +++ b/mcp-server/src/legal_mcp/server.py @@ -290,10 +290,16 @@ async def style_corpus_pending_enrichment(limit: int = 50) -> str: return await train_tools.list_corpus_pending_enrichment(_clamp_limit(limit)) +@mcp.tool() +async def extraction_status() -> str: + """סטטוס תור-החילוץ — כמה פסיקות ממתינות לחילוץ metadata/halacha + גיל הבקשה הוותיקה. read-only (חושף את התור ש-precedent_process_pending מרוקן).""" + return await plib.extraction_status() + + @mcp.tool() async def precedent_process_pending(kind: str = "metadata", limit: int = 20) -> str: """ריקון תור בקשות חילוץ שנשלחו מ-UI. kind: 'metadata' או 'halacha'. מריץ extractor מקומית עם CLI על כל פריט בתור, ומנקה את הסימון אחרי הצלחה.""" - return await plib.precedent_process_pending(kind, limit) + return await plib.precedent_process_pending(kind, _clamp_limit(limit)) @mcp.tool() diff --git a/mcp-server/src/legal_mcp/services/db.py b/mcp-server/src/legal_mcp/services/db.py index 2ed7bf0..f8b18cd 100644 --- a/mcp-server/src/legal_mcp/services/db.py +++ b/mcp-server/src/legal_mcp/services/db.py @@ -4332,6 +4332,31 @@ async def list_pending_extraction_requests( return out +async def extraction_queue_status() -> dict: + """Pending-extraction queue depth per kind (INV-TOOL4 visibility / GAP-45). + + Surfaces the otherwise-hidden queue that ``process_pending_extractions`` + drains: how many case_law rows still carry a metadata/halacha extraction + request, and the age of the oldest one. Read-only — does not drain. + """ + pool = await get_pool() + async with pool.acquire() as conn: + meta = await conn.fetchrow( + "SELECT COUNT(*) AS n, MIN(metadata_extraction_requested_at) AS oldest " + "FROM case_law WHERE metadata_extraction_requested_at IS NOT NULL" + ) + hal = await conn.fetchrow( + "SELECT COUNT(*) AS n, MIN(halacha_extraction_requested_at) AS oldest " + "FROM case_law WHERE halacha_extraction_requested_at IS NOT NULL" + ) + + def _fmt(r: dict) -> dict: + oldest = r["oldest"] + return {"pending": r["n"], "oldest_request": oldest.isoformat() if oldest else None} + + return {"metadata": _fmt(meta), "halacha": _fmt(hal)} + + async def clear_extraction_request( case_law_id: UUID, kind: str = "metadata", ) -> None: diff --git a/mcp-server/src/legal_mcp/tools/precedent_library.py b/mcp-server/src/legal_mcp/tools/precedent_library.py index 2f16347..640d17c 100644 --- a/mcp-server/src/legal_mcp/tools/precedent_library.py +++ b/mcp-server/src/legal_mcp/tools/precedent_library.py @@ -233,6 +233,19 @@ async def precedent_reindex(case_law_id: str) -> str: return _ok(result) +async def extraction_status() -> str: + """סטטוס תור-החילוץ — כמה פסיקות ממתינות לחילוץ metadata/halacha (INV-TOOL4 / GAP-45). + + חושף את התור ש-precedent_process_pending מרוקן: עומק-תור + גיל הבקשה + הוותיקה ביותר לכל סוג. read-only — אינו מרוקן את התור. + """ + try: + status = await db.extraction_queue_status() + except Exception as e: + return _err(str(e)) + return _ok(status) + + async def precedent_process_pending(kind: str = "metadata", limit: int = 20) -> str: """ריקון תור בקשות חילוץ שנערמו ע"י כפתורי ה-UI. kind: 'metadata' או 'halacha'. -- 2.49.1