Merge pull request 'feat(mcp): FU-14 GAP-45 — extraction_status (חשיפת תור-החילוץ הסמוי)' (#64) from fix/fu14-gap45-extraction-status into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m37s

This commit was merged in pull request #64.
This commit is contained in:
2026-06-06 15:00:50 +00:00
5 changed files with 48 additions and 4 deletions

View File

@@ -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 היכן שרלוונטי. **הנדסי.**

View File

@@ -199,7 +199,7 @@
- **מכסה:** GAP-44,45,47..54 · **invariants:** INV-TOOL1TOOL5 · **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-ENV1ENV5 · **effort:** M · **תלויות:**

View File

@@ -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()

View File

@@ -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:

View File

@@ -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'.