Files
legal-ai/docs/spec/X13-court-fetch.md
Chaim e186183527 fix(X13): harden court-fetch against browser leaks + reaper for task-master-mcp leak
שלוש שכבות-הגנה נגד דליפת-זיכרון מדפדפנים יתומים, + טיפול בדליפה הגדולה
בפועל בשרת (task-master-mcp).

- camofox_client.py:
  - asyncio.wait_for קשיח סביב כל ה-fetch (COURT_FETCH_HARD_TIMEOUT_S=180ש')
    — hang → ביטול → async-with tear-down → reap.
  - _reap_orphan_browsers(): הורג camoufox-bin יתומים (ppid=1) לפני ואחרי כל
    fetch. סדרתיות (INV-CF4) → כל ppid=1 הוא שארית בטוחה.
- scripts/reap_orphan_procs.py: reaper כללי ל-task-master-mcp (~3GB יתומים)
  + camoufox-bin. רק ppid=1; /proc טהור. --dry-run / --loop N.
- scripts/legal-reaper.config.cjs: דמון pm2 (loop 180s, max_memory_restart 100M).
- X13 spec + SCRIPTS.md: תיעוד שכבות-ההגנה.

max_memory_restart בשירות (1.5G) כבר נותן רשת-ביטחון ברמת-התהליך.
Invariants: מקיים INV-CF4 (politeness/serial) — ללא שינוי חוזה.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:43:53 +00:00

13 KiB
Raw Blame History

X13 — אחזור-פסיקה אוטומטי מנט המשפט (Court Verdict Fetch)

כפוף ל-חוקת המערכת. תת-מערכת שירות (לא קורפוס) שמורידה פסקי-דין ציבוריים של בתי-משפט ומזרימה אותם לצינור-הקליטה הקנוני של ספריית-הפסיקה. אחות-מושגית ל-X12 — Digests Radar (הטריגר העיקרי) ול-01-ingest (היעד). אינה קורפוס רביעי ואינה מסלול-ingest מקביל.


0. ייעוד והקשר

יומון (digest) מצביע על פסק-דין נושא (underlying_citation, למשל עת"מ 46111-12-22). כשהפסק אינו בקורפוס, המערכת מאחזרת אותו אוטומטית ממקור ציבורי, מחלצת טקסט, וקולטת אותו דרך precedent_library_uploadingest_precedent. כך הופך פסק-דין מ"מצוטט-בלבד" ל"שמיש לחיפוש וחילוץ-הלכות".

הבחנת-מקור קריטית: רק פסקי-דין של בתי-משפט ניתנים לאחזור ציבורי. החלטות ועדת-ערר אינן זמינות ציבורית (נדרש נבו) — מסומנות כפער ולא נשלחות לאחזור.

שתי דרכי-מקור ציבוריות:

  • עליון (עע"מ/בג"ץ/ע"א/רע"א/בר"מ/דנ"א) → supremedecisions.court.gov.il — הורדה ישירה (httpx), ללא CAPTCHA.
  • מנהלי/מחוזי/שלום (עת"מ/עמ"נ/...) → מציג-התיקים של נט המשפט — ASP.NET WebForms (__doPostBack/VIEWSTATE), anti-bot של F5, מסמכים מוצגים בצופה-עמודים (turn.js). מחייב דפדפן-אמת (host-side), ולכן שירות-מארח ב-pm2 (כדפוס legal-chat-service).

אומת end-to-end (2026-06-07) על עת"מ 46111-12-22 — פס"ד 34 עמ' הורד אוטונומית מלא, נטו קוד-פתוח, ללא כרטיס-חכם וללא פתרון-CAPTCHA. ממצאי-המפתח מהכיול:

  • החיפוש והניווט לתיק — ללא reCAPTCHA כלל. מסלול: דף-בית → btnExternalSearchCases → מילוי BamaCaseNumberTextBoxH(=מס' תיק) + BamaMonthYearTextBoxHT(="MM-YY") → CaseDetails.aspx → לשונית "פסקי דין" → DecisionList.aspx → צופה NGCSViewerPage.aspx.
  • reCAPTCHA קיים רק בצופה ורק על שמירה/הדפסה מפורשתלא על הצגת המסמך. הצופה מגיש את העמודים כ-PNG דרך PageMethod GetImages (4 עמ'/batch) ללא CAPTCHA. אחזור = לכידת documentNumber מהקריאה הראשונה + משיכת כל ה-batches ב-fetch עם הכותרת X-Requested-With: XMLHttpRequest (חובה — ה-WAF חוסם AJAX בלעדיה) → הרכבת PDF (Pillow).
  • דפדפן: Camoufox דרך חבילת-הפייתון (camoufox.async_api, in-process — לא שרת-Node). על שרת ללא-מסך נדרש Xvfb (אחרת Firefox קורס). פותר-ה-reCAPTCHA האודיו (Whisper) נשמר כ-fallback למסלול-השמירה-המפורש בלבד; מסלול-התמונות אינו זקוק לו.

1. ארכיטקטורה — שלוש שכבות (tiered)

underlying_citation → [classifier] → tier ∈ {supreme, admin, skip}
  skip(ערר/בל"מ)  → missing_precedent (נבו ידני)                — לא אחזור
  supreme         → Tier 0: httpx בקונטיינר → supremedecisions   — אוטונומי מלא
  admin           → Tier 1: legal-court-fetch-service (host/pm2 + Xvfb)  — אוטונומי-first
                       → Camoufox(python) → external-search → CaseDetails → פסקי דין
                       → NGCSViewerPage → GetImages(X-Requested-With) → PNGs → PDF
                    → Tier 2 fallback: VNC ידני / missing_precedent + התראה  — שער-אנושי
  (כל ה-tiers)    → precedent_library_upload(source_type=court_ruling) → ingest_precedent
                       → chunks+embeddings+halachot(pending) → relink digest / close gap

מצב-העבודה מנוהל בטבלת-תור court_fetch_jobs (idempotent, נצפה, retryable).


2. Invariants

INV-CF1: מסלול-קליטה יחיד — אין ingest מקביל

כלל: כל ה-tiers מתנקזים לצינור-הקליטה הקנוני היחיד (precedent_library_uploadingest_precedent). המאחזר מספק קובץ+מטא בלבד; אסור לו לכתוב case_law/precedent_chunks/ halachot ישירות או לשכפל לוגיקת-chunking/embedding. מקור-סמכות: פרויקטלי-תפעולי — מיישם את G2 (מקור-אמת יחיד, אין מסלול מקביל) על תת-מערכת זו. אכיפה: האורקסטרטור קורא רק ל-API/שירות-הקליטה הקיים; ביקורת-ארכיטקטורה ב-PR. הפרה ידועה:

INV-CF2: אין בליעה שקטה — כל אחזור נצפה

כלל: לכל פסק-דין שזוהה לאחזור יש רשומת-job עם סטטוס סופי מפורש (done/failed/manual). כישלון-אחזור לעולם אינו נבלע — הוא מסומן ומועלה (Tier 2), לא נזרק בשקט. except: pass אסור. מקור-סמכות: פרויקטלי-תפעולי — מיישם את G4 וכלל-ההנדסה "אין בליעה שקטה" (§6). אכיפה: טבלת court_fetch_jobs (status+error+attempts) + לוג-warning בכל כישלון + Tier-2 gate. הפרה ידועה: הפער הקיים ב-X12 — try_autolink שנכשל מחזיר None בשקט (יתוקן ע"י טריגר זה).

INV-CF3: אוטונומי-first, שער-אנושי חובה ב-fallback

כלל: האחזור מנסה אוטונומית; אך כש-N נסיונות נכשלים, שער-אנושי (VNC לפתרון-CAPTCHA חי / סימון missing_precedent + התראה) הוא חובה, לא רשות. המערכת אינה "מוותרת" ואינה "מסתירה" — היא מסלימה לאדם. מקור-סמכות: פרויקטלי-תפעולי — מיישם את G10 (המערכת מסייעת; שערים אנושיים = invariant). אכיפה: מונה-נסיונות בטבלת-התור + מעבר אוטומטי ל-status=manual עם נתיב-פעולה ל-chaim. הפרה ידועה:

INV-CF4: אחזור-אחראי (politeness) — סדרתי, מרווח, חתימה-אמיתית

כלל: האחזור מאתר-ממשלתי הוא אחראי: סדרתי (לא מקבילי), עם cooldown בין בקשות, כיבוד-robots/תנאי-שימוש, ו-rate מתון. אסור flooding/parallel-hammering שעלול לחסום IP או להעמיס על שירות ציבורי. מקורות: RFC 9309 (Robots Exclusion Protocol, IETF 2022) · Google Search Central — Crawler / crawl-rate guidance · OWASP — Automated Threat Handbook (OAT-021 Denial of Service / responsible automation) | סטטוס: verified אכיפה: האורקסטרטור והשירות אוכפים serial + INTER_FETCH_COOLDOWN_SEC; Camoufox מספק חתימת-דפדפן אמיתית (לא spoof-חמדני). מראה לדפוס-התור ב-precedent_library.py. הפרה ידועה:

INV-CF5: אחזור idempotent

כלל: אחזור הוא idempotent — מפתח-job דטרמיניסטי לפי case_number מנורמל. אחזור חוזר של אותו תיק אינו יוצר job כפול ואינו קולט פסק-דין פעמיים (upsert על המפתח הקנוני). מקור-סמכות: פרויקטלי-תפעולי — מיישם את G3 (ingest idempotent) ו-G1 (מזהה מנורמל בכתיבה). אכיפה: אילוץ-ייחודיות על court_fetch_jobs.case_number_norm; הקליטה עצמה idempotent דרך ingest_precedent. הפרה ידועה:

INV-CF6: שער-סיווג מקור — רק פסקי-דין של בתי-משפט

כלל: רק ציטוט שסווג כפסק-דין של בית-משפט נשלח לאחזור. ועדת-ערר (ערר/בל"מ) לעולם אינה נשלחת לאחזור-ציבורי (נדרש נבו) — היא מסומנת missing_precedent בלבד. הפריט הנקלט נושא source_type=court_ruling, source_kind=external_upload, precedent_level לפי הערכאה. מקור-סמכות: פרויקטלי-תפעולי — מיישם את G5 (metadata מלא + הפרדת-קורפוס) ותואם את הבחנת-המקור ב-01-ingest (court_ruling מול appeals_committee). אכיפה: המסווג מחזיר tier=skip ל-ערר/בל"מ; הקליטה אוכפת source_type. הפרה ידועה:

INV-CF7: עקיבוּת-מקור + גבול-ToS

כלל: כל אחזור נושם provenance מלא (source_url, tier, זמן, מזהה-job) ב-audit-trail. האחזור מוגבל למסמכים ציבוריים הזמינים ללא הזדהות (smart-card); אופי המערכת הוא הורדה-בסיוע (עם שער-אנושי), לא בוט-סמוי לעקיפת בקרת-גישה. מקור-סמכות: פרויקטלי-תפעולי — מיישם את G9 (עקיבוּת + audit-trail); גבול-ה-ToS מועלה ליו"ר (חיים) כשיקול-מדיניות (עיקרון-עבודה 4: המשתמש הוא הסמכות). אכיפה: source_url+tier נשמרים על case_law/court_fetch_jobs; שער-אנושי שומר על אופי בסיוע. הפרה ידועה:


3. מודל-נתונים — court_fetch_jobs

עמודה טיפוס תפקיד
id UUID PK מזהה-job
case_number_norm TEXT UNIQUE מפתח-idempotency קנוני (INV-CF5)
citation_raw TEXT הציטוט המקורי כפי שזוהה
tier TEXT supreme | admin | skip
court TEXT ערכאה שזוהתה
status TEXT pending | running | done | failed | manual
attempts INT מונה-נסיונות (ל-Tier 2 gate, INV-CF3)
error TEXT הודעת-כישלון אחרונה (INV-CF2)
case_law_id UUID FK הפסק שנקלט (NULL עד done)
digest_id UUID FK היומון-מקור (NULL לאד-הוק)
source_url TEXT provenance (INV-CF7)
created_at / updated_at TIMESTAMPTZ

4. רכיבי-מימוש (מיפוי לקוד)

רכיב קובץ מקור-תבנית / שימוש-חוזר
מסווג mcp-server/.../services/court_citation.py regex מ-citation_extractor.py:67-132
Tier 0 services/court_fetch_supreme.py httpx; דפוס-cooldown מ-precedent_library.py:176-186
Tier 1 שירות mcp-server/.../court_fetch_service/server.py שכפול chat_service/server.py (aiohttp+Bearer+bind 10.0.1.1)
Camoufox client court_fetch_service/camofox_client.py חיקוי ~/.hermes/.../browser_camofox.py
reCAPTCHA audio court_fetch_service/recaptcha_audio.py faster-whisper מקומי
proxy בקונטיינר web/court_fetch_proxy.py שכפול web/chat_proxy.py
pm2 scripts/legal-court-fetch-service.config.cjs שכפול legal-chat-service.config.cjs
אורקסטרטור+תור services/court_fetch_orchestrator.py + db.py (SCHEMA_Vxx) דפוס-תור קיים
כלי-MCP tools/court_fetch.py (court_verdict_fetch) חוזה-envelope X9
טריגר services/digest_library.py (try_autolink fail-path) X12
סוד COURT_FETCH_SHARED_SECRET (Infisical + Coolify) דפוס LEGAL_CHAT_SHARED_SECRET, X10

5. סיכונים (R&D — לעקוב)

  • reCAPTCHA נלחם פעיל בפותרי-אודיו → שיעור-כישלון אפשרי גבוה → Tier 2 הוא קו-ההגנה (INV-CF3).
  • F5/anti-bot עלול לחסום IP → politeness סדרתי + Camoufox (INV-CF4).
  • שבירות מול שינויי-אתר → ריכוז selectors במקום אחד + בדיקות-עשן תקופתיות.
  • גבול-ToS על אתר .gov → INV-CF7 + שיקול-יו"ר.
  • דליפת-זיכרון מדפדפנים יתומים (fetch שנתקע/נהרג משאיר camoufox-bin) → שלוש שכבות-הגנה: (א) async with סוגר את הדפדפן בכל exception; (ב) asyncio.wait_for קשיח (COURT_FETCH_HARD_TIMEOUT_S, ברירת-מחדל 180ש') מבטל hang + reap; (ג) reaper של camoufox-bin יתומים (ppid=1) לפני/אחרי כל fetch + דמון legal-reaper (pm2) + תקרת max_memory_restart. סדרתיות (INV-CF4) מבטיחה שכל דפדפן ppid=1 הוא שארית בטוחה-להריגה. הערה: הדליפה הגדולה בפועל בשרת היא task-master-mcp (כלי נפרד), שגם אותו ה-reaper מנקה.