feat(mcp): FU-14 פרוסה 1 — get_appraiser_facts (GAP-44) + limit-caps (GAP-53)

תוספתי בלבד, אפס שבירת-תאימות. שני invariants מחוזה-כלי-ה-MCP (X9):

GAP-44 (INV-TOOL4, סימטריית extract/get): נוסף get_appraiser_facts — ה-get
המקביל ל-extract_appraiser_facts. קורא list_appraiser_facts + detect_appraiser_conflicts
מה-DB ללא חילוץ-LLM יקר ולא-דטרמיניסטי. מחזיר count=0 (לא שגיאה) אם טרם חולץ.

GAP-53 (INV-TOOL5, limit-caps / OWASP API4:2023): נוסף _clamp_limit (תקרה 200,
non-positive→max) על ~13 כלי list/search ב-server.py (case_list, search_*,
precedent_library_list, halachot_pending, missing_precedent_list, list_*_citations…).
list_chair_feedback קיבל param limit חדש (server→workflow→db עם LIMIT) — היה ללא תקרה כלל.

לא הוסף get_appraiser_facts ל-frontmatter של סוכנים (INV-AG3 "לא עודף" — ההוראות
עוד לא מפנות אליו; חיווט = follow-up). נותר ב-FU-14: GAP-45/48/49/50/51/52.

עודכנו docs/spec/X9 (INV-TOOL4/5) ו-gap-audit (סטטוס פרוסה 1).

אומת: py_compile על 4 קבצי הקוד. אימות runtime (restart MCP server) נדחה עד
שהחילוץ הפעיל של היו"ר יסתיים.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-06 14:37:30 +00:00
parent 67a3d9a9b0
commit ebfe7f6a1d
6 changed files with 78 additions and 20 deletions

View File

@@ -84,10 +84,24 @@ async def case_create(
)
# INV-TOOL5 / GAP-53: hard cap on list/search result sizes (OWASP API4:2023 —
# Unrestricted Resource Consumption). Non-positive is treated as "max", not "all".
_MAX_LIMIT = 200
def _clamp_limit(limit: int, hard_max: int = _MAX_LIMIT) -> int:
"""Clamp a caller-supplied result limit to [1, hard_max]."""
try:
n = int(limit)
except (TypeError, ValueError):
return hard_max
return hard_max if n <= 0 else min(n, hard_max)
@mcp.tool()
async def case_list(status: str = "", limit: int = 50) -> str:
"""רשימת תיקי ערר. סינון אופציונלי לפי סטטוס (new/in_progress/drafted/reviewed/final)."""
return await cases.case_list(status, limit)
return await cases.case_list(status, _clamp_limit(limit))
@mcp.tool()
@@ -162,7 +176,7 @@ async def precedent_search_library(
) -> str:
"""חיפוש בציטוטים שדפנה צירפה ידנית לתיקים בעבר (case_precedents).
שונה מ-search_precedent_library שמחפש בקורפוס הפסיקה הסמכותית."""
return await precedents.precedent_search_library(query, practice_area, limit)
return await precedents.precedent_search_library(query, practice_area, _clamp_limit(limit))
# ── External Precedent Library — authoritative case-law corpus ─────
@@ -214,7 +228,7 @@ async def precedent_library_list(
"""
return await plib.precedent_library_list(
practice_area, court, precedent_level, source_type, search,
source_kind, limit,
source_kind, _clamp_limit(limit),
)
@@ -273,7 +287,7 @@ async def style_corpus_enrich(corpus_id: str, overwrite: bool = False) -> str:
@mcp.tool()
async def style_corpus_pending_enrichment(limit: int = 50) -> str:
"""רשימת החלטות בקורפוס הסגנון שעדיין חסרות summary/outcome/key_principles — מועמדות לחילוץ."""
return await train_tools.list_corpus_pending_enrichment(limit)
return await train_tools.list_corpus_pending_enrichment(_clamp_limit(limit))
@mcp.tool()
@@ -296,7 +310,7 @@ async def search_precedent_library(
"""חיפוש סמנטי בקורפוס הפסיקה הסמכותית. מחזיר הלכות (מאושרות בלבד) + קטעי טקסט. השתמש כש-legal-writer צריך לצטט פסיקה מחייבת בבלוק י (CREAC: rule + explanation)."""
return await plib.search_precedent_library(
query, practice_area, court, precedent_level, appeal_subtype,
None, subject_tag, limit, include_halachot,
None, subject_tag, _clamp_limit(limit), include_halachot,
)
@@ -320,7 +334,7 @@ async def halacha_review(
@mcp.tool()
async def halachot_pending(limit: int = 100) -> str:
"""תור ההלכות הממתינות לאישור."""
return await plib.halachot_pending(limit)
return await plib.halachot_pending(_clamp_limit(limit))
# Documents
@@ -439,7 +453,7 @@ async def search_decisions(
) -> str:
"""חיפוש סמנטי בהחלטות קודמות ובמסמכים — מסונן לפי תחום משפטי."""
return await search.search_decisions(
query, limit, section_type, practice_area, appeal_subtype, case_number,
query, _clamp_limit(limit), section_type, practice_area, appeal_subtype, case_number,
)
@@ -450,7 +464,7 @@ async def search_case_documents(
limit: int = 10,
) -> str:
"""חיפוש סמנטי בתוך מסמכי תיק ספציפי."""
return await search.search_case_documents(case_number, query, limit)
return await search.search_case_documents(case_number, query, _clamp_limit(limit))
@mcp.tool()
@@ -463,7 +477,7 @@ async def find_similar_cases(
) -> str:
"""מציאת תיקים דומים על בסיס תיאור — מסונן לפי תחום משפטי."""
return await search.find_similar_cases(
description, limit, practice_area, appeal_subtype, case_number,
description, _clamp_limit(limit), practice_area, appeal_subtype, case_number,
)
@@ -496,7 +510,7 @@ async def search_internal_decisions(
כשרוצים להרחיב מעבר לטקסט המקורי. default False.
"""
return await search.search_internal_decisions(
query, practice_area, appeal_subtype, district, chair_name, limit, include_halachot,
query, practice_area, appeal_subtype, district, chair_name, _clamp_limit(limit), include_halachot,
include_cited_by=include_cited_by,
)
@@ -571,6 +585,12 @@ async def extract_appraiser_facts(case_number: str) -> str:
return await drafting.extract_appraiser_facts(case_number)
@mcp.tool()
async def get_appraiser_facts(case_number: str) -> str:
"""קריאת עובדות-השמאי שכבר חולצו (facts + סתירות) — ללא חילוץ-מחדש יקר. ה-get המקביל ל-extract_appraiser_facts."""
return await drafting.get_appraiser_facts(case_number)
@mcp.tool()
async def write_interim_draft(case_number: str, instructions: str = "") -> str:
"""כתיבת ארבעת הבלוקים לטיוטת ביניים (רקע, תכניות+היתרים, טענות, הליכים) — אותו skill וטמפלט."""
@@ -813,7 +833,7 @@ async def missing_precedent_list(
case_number=case_number,
status=status,
legal_topic=legal_topic,
limit=limit,
limit=_clamp_limit(limit),
)
@@ -878,7 +898,7 @@ async def list_internal_citations(
return await cit_tools.list_internal_citations(
case_law_id=case_law_id,
linked_only=linked_only,
limit=limit,
limit=_clamp_limit(limit),
)
@@ -894,7 +914,7 @@ async def list_incoming_citations(
"""
return await cit_tools.list_incoming_citations(
case_law_id=case_law_id,
limit=limit,
limit=_clamp_limit(limit),
)
@@ -917,9 +937,10 @@ async def list_chair_feedback(
case_number: str = "",
category: str = "",
unresolved_only: bool = True,
limit: int = 100,
) -> str:
"""הצגת הערות יו"ר שתועדו — אפשר לסנן לפי תיק, קטגוריה, מטופלות."""
return await workflow.list_chair_feedback(case_number, category, unresolved_only)
return await workflow.list_chair_feedback(case_number, category, unresolved_only, _clamp_limit(limit))
@mcp.tool()