Commit Graph

327 Commits

Author SHA1 Message Date
4994ae0cba Merge pull request 'feat(plans): משיכת תב"ע מ-מנהל-התכנון (mavat) — Phase C backend-slice' (#292) from worktree-plan-fetch-mavat into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-17 11:14:44 +00:00
a55ffd59eb feat(plans): משיכת תב"ע מ-מנהל-התכנון (mavat) — Phase C backend-slice
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
ליבת-המשיכה למרשם-התכניות (V38): מספר-תכנית → זהות+תוקף מ-mavat דרך
גשר-Camoufox הקיים (G2 — אותו שירות/פורט/סוד כמו X13, בלי חדשים).

- court_fetch_service/mavat_client.py (חדש): דרייבר Camoufox מול mavat —
  עוקף F5-ASM (דפדפן-JS), search→auto-nav ל-SV4, לוכד GET /rest/api/SV4/1,
  מפענח planDetails (E_NAME/AUTH/ENTITY_SUBTYPE/GOALS) + rsInternet
  (פרסום-לאישור→ED_PUBLICATION_FILE=י"פ + DETAILS→תאריך/עמוד). מלכודת-
  דרייבר: init-script window.onerror swallow. reCAPTCHA נשאר דלוק (token).
- court_fetch_service/server.py: POST /plan-fetch (אותו Bearer).
- services/plans_fetch.py (חדש): צד-קונטיינר — httpx לגשר, מנרמל שדות.
- tools/plans.py + server.py: כלי-MCP plan_fetch (מועמד, לא כותב).
- web/app.py: POST /api/plans/fetch (503 גשר-למטה, 404 לא-נמצא).

אומת חי מול mavat: 101-1031020→י"פ 13697 (עמ' 8758, 30/07/2025),
101-1053933→י"פ 13836. מקור-אמת עשיר מתב"ע-עכשיו (שחסר י"פ).

INV-AH: כל ערך נושא source_url; שדה-חסר ריק לא מומצא. G10: מחזיר
מועמד בלבד — שער-יו"ר (review_status) נשמר. G2: מרחיב גשר+מרשם קיימים.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 11:09:39 +00:00
32db9621b6 feat(ui): עיצוב מחדש של טאב הסקירה בדף תיק
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
- הסרת כרטיס "סקירת התיק" הדל (3 אלמנטים בלבד)
- כפתור "התחל תהליך" עלה לפס-הפעולות בראש הדף (ליד "העלאת מסמכים")
- כרטיס חדש AgentActivityPreview: 4 הissues האחרונים + קישור לטאב מלא
- "תוצאה צפויה" ירדה לשורת מטא-דאטה בראיל הסטטוס (מוצגת רק אם נקבעה)
- תיקון באג: expected_outcome לא נשמר — guard if expected_outcome: הוחלף ב-is not None

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 11:06:10 +00:00
08a0eb7c01 Merge pull request 'refactor(cases): צמצום תפריט-סטטוס 17→10 + מקור-אמת יחיד (UI-B1/G2)' (#287) from worktree-status-trim into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m29s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-17 10:15:41 +00:00
406e93b9bf fix(precedents): חילוץ-מטא-דאטה ממלא תחום (practice_area) ושם-יו"ר לכל החלטת-ועדה
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
שני פערים שצפו מ-/precedents בחילוץ-ההלכות:

1. **practice_area לא סומן** — השדה הועבר ל-LLM כקונטקסט-קריאה-בלבד ולא חולץ
   מעולם, כך שהעלאות שהשאירו אותו ריק נשארו ריקות והרדיו ב-/precedents הופיע
   ללא בחירה. עכשיו נגזר ב-apply_to_record: עדיפות לגזירה דטרמיניסטית מקידומת
   מספר-התיק (1xxx→rishuy, 8xxx→היטל, 9xxx→197 — מקור-אמת לדוקטי ועדת-ערר,
   INV-AH rule-based), ובנפילה — סיווג-תוכן של ה-LLM (שדה practice_area חדש
   בפרומפט, אנום-סגור) עבור פסקי-בית-משפט שהקידומת שלהם אינה מקודדת תחום.
   ממלא רק כשריק (G1 — נרמול במקור, לא תיקון-בקריאה).

2. **שם-יו"ר לא חולץ** (למשל 1132-09-24) — המיזוג היה מגודר על
   source_kind=='internal_committee' בלבד, ודילג בשקט על החלטות-ועדה שהועלו
   במסלול הפסיקה החיצוני (external_upload + source_type=appeals_committee, כמו
   החלטת ת"א מנבו) — היו"ר ישב בבלוק-החתימה אך לא חולץ. עכשיו מגודר על "האם זו
   החלטת-ועדה" (source_type/level אפקטיביים), לעולם לא על פסק-בית-משפט. ה-CHECK
   כופה non-empty רק ל-internal_committee, לכן כתיבה ל-external בטוחה.

חיזוק-פרומפט (לבקשת היו"ר): chair_name מציין מפורשות את בלוק-החתימה הדו-טורי
(מזכיר↔יו"ר — לקחת את צד-היו"ר) ומזהיר לא לחלץ יו"ר של פסקי-דין **מצוטטים**
בגוף ההחלטה.

UI (לוגיקה-בלבד, פטור משער-העיצוב): edit-sheet מסנכרן-מחדש מהרשומה הטרייה בכל
פתיחה (re-arm על סגירה) ו-usePrecedent עושה poll בזמן חילוץ — כך מילוי-רקע של
practice_area/chair_name מופיע בלי refresh מלא ("הכפתור לא נשאר מסומן").

בדיקות: test_metadata_extract_chair_practice_area.py (6 תרחישי-מיזוג, offline).

Invariants: G1 (נרמול-במקור), G2 (אותו extractor, לא מסלול מקביל),
INV-AH (גזירה דטרמיניסטית מועדפת, abstention כשאין ודאות).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:53:42 +00:00
ba542f9c21 refactor(cases): צמצום תפריט-סטטוס 17→10 + מקור-אמת יחיד (UI-B1/G2)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
תפריט הסטטוס-הידני הכיל 17 סטטוסים שמתוכם ~9 דקורציה טהורה — שלבי-ביניים
שאף קוד בפייפליין לא קבע ושום לוגיקה לא הסתעפה לפיהם, עם רשימות כפולות
לא-עקביות ב-6+ קבצים (UI-B1) ו-exported כסטטוס-רפאים (באג agent-audit).

הליבה (10): new, processing, documents_ready, outcome_set, direction_approved,
qa_review, drafted, exported, reviewed, final.

- SSoT חדש web-ui/src/lib/api/case-status.ts (רשימה/שלבים/תוויות/statusLabel);
  כל הצרכנים (badge/changer/timeline/guide/donut/kpi/compose) מייבאים משם.
- statusLabel() מבטיח תווית עברית תמיד — גם לערך-מורשת (נפילה עברית, לא סלאג).
- בקאנד: STATUS_ORDER 10, models.CaseStatus מיושר, set_outcome קובע
  outcome_set/direction_approved (במקום in_progress) כמו endpoint ה-web.
- exported מוקשח אחרי export-DOCX מוצלח (forward-only); widget "נכשל ב-QA"
  עודכן ל-qa_review (הסטטוס שנקבע בפועל בכשל-QA).
- scripts/backfill_case_status_trim.py: מיפוי שורות-מורשת לסטטוס-הליבה הקודם.

Invariants: UI-B1 (מקור-אמת יחיד)  · G2 (אין מסלול מקביל)  · GAP-42 (חלקי).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 09:47:13 +00:00
e7124e94a3 Merge pull request 'feat(scripts): כפתור "הרץ" מ-UI לסקריפטי read-only (קטגוריה B #4)' (#283) from worktree-scripts-run-ui into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m34s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-17 04:31:19 +00:00
221975fe23 feat(scripts): כפתור "הרץ" מ-UI לסקריפטי read-only (קטגוריה B #4)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
הרצת-סקריפט-מ-UI ב-/scripts, רק לסקריפטי קריאה-בלבד/אודיט, דרך גשר-המארח
הקיים (court-fetch) — שיכפול דפוס /adapter-migration.

אבטחה:
- allowlist בצד-המארח (services/script_runner.py, מקור-אמת יחיד): name→argv
  קבוע ובטוח. 5 סקריפטים מאומתים: leak_guard, check_undefined_names,
  storage_leak_tripwire, audit_training_corpus, audit_corpus_integrity --no-notify.
- הגשר (court_fetch_service/server.py): endpoint POST /run-script, Bearer-auth,
  ולידציית-allowlist, create_subprocess_exec (ללא shell), timeout 600s,
  audit-log; מתעלם מארגומנטים מהבקשה (argv מה-allowlist בלבד).
- קונטיינר (web/app.py): POX /api/scripts/{name}/run proxy ל-גשר + pre-check
  allowlist; הרחבת /api/scripts/catalog ב-runnable_scripts.
- UI: כפתור "הרץ" (ירוק) רק לשורות-runnable + דיאלוג-פלט (exit-code+stdout);
  שאר השורות "מקור" בלבד. confirm לפני הרצה.

מאושר דרך שער-העיצוב (מוקאפ 16-scripts עודכן עם כפתור "הרץ").
G12: leak_guard עובר (אין סמלי-פלטפורמה בשכבת-האינטליגנציה).

Deploy דו-שלבי: גשר-המארח דורש git pull בעותק-המארח + pm2 restart
legal-court-fetch-service; הקונטיינר נפרס דרך Coolify כרגיל.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 04:30:43 +00:00
896df0cb8c feat(halachot): חיפוש/איתור בתור-ההלכות + הערת חלון-תצוגה (פסיקה מחוץ ל-500 נגישה)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
תור-ההלכות שלף רק 500 ממתינות בעלות-עדיפות מתוך ~1,372, בלי שום דרך
לאתר פס"ד מסוים. הלכה מדורגת מתחת לחלון (למשל 1180-11-25, מקומות 921/1305)
פשוט נעלמה — הספירה בספרייה הציגה "2 ממתינות" אך התור לא הראה אותן.

- list_halachot: פרמטר search (case_number/case_name/rule_statement, ILIKE)
  — סינון בצד-השרת כך שפריט מתחת לחלון נשאר נגיש. מרחיב את מסלול-השליפה
  היחיד, לא יוצר מסלול מקביל (G2).
- count_halachot: ספירה מלאה לאותו פילטר (ל-"N מתוך TOTAL").
- /api/halachot: search + with_total (ברירת-מחדל off; קוראים קיימים לא מושפעים).
- UI (תור-הלכות): שורת-חיפוש מהוקצבת (debounce 300ms), הערת "חלון התצוגה"
  (500 מתוך הסך), ושורת-הקשר-תוצאה עם ניקוי. בחיפוש — כל הקבוצות התואמות
  נפתחות; הניווט המקלדתי והשערים (G10) ללא שינוי.

מאומת מול ה-DB: search='1180-11-25' → 2 הממתינות (index 20 נקייה→להכרעתך,
index 23 nli_unsupported→דורש תיקון-חילוץ); count=2.

עיצוב: עבר שער Claude Design (X17, כרטיס 19-halacha-queue-unified) ואושר.
Invariants: מקיים G2 (מסלול-שליפה יחיד), INV-G10 (שער-יו"ר ללא שינוי),
INV-IA (מקור-אמת יחיד לתור). ללא בליעת-שגיאות.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 03:58:46 +00:00
0c78e30e07 fix(halachot): ספירת-תור אמיתית + עדכון-חי בתגי-הכרעה (#6/#7/#8)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
המספרים בתגי תור-ההלכות היו תקרות-שאילתה ולא ספירה אמיתית:
- "ממתינות 500" = pendingData.items.length עם limit=500
- "נדחו 1000 / אושרו 1000" = useHalachotByStatus(...,1000) — תקרה 1000
ובלי refetchInterval התגים התעדכנו רק בכניסה לדף.

המקור האמיתי כבר קיים: /api/precedent-library/stats מריץ COUNT(*) אמיתי
(pending=1373, approved=2100). מוסיף לו halachot_rejected + halachot_deferred,
מחבר את תגי-ה-HalachaReviewPanel למקור הזה, ומוסיף polling (30s) כדי שהם
יתעדכנו חי. מסיר את useHalachaCount המיותר.

תור-העבודה עצמו עדיין נטען עד 500 פריטים (cap-עבודה לגיטימי); רק תצוגת
הספירות תוקנה להציג את הסך-האמיתי.

Invariants: מקיים G1 (נרמול-במקור — ספירה אמיתית מ-COUNT(*) במקום len(rows)
מתוקרת בקריאה) ו-G2 (מאחד על מקור-הספירה הקיים, ללא endpoint-ספירה מקביל).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:48:10 +00:00
fc02ccaeff feat(missing-precedents): עמודת "צוטט ע"י" מציגה provenance לפי discovery_source (#148)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
362 רשומות שהובאו מ-cited_only (גרף-הציטוטים) ומיומונים הציגו "—" ב"צוטט ע"י",
כי העמודה קראה רק מ-cited_in_case_id (ערר חי) + cited_by_party, וה-provenance
נשמר ב-notes בלבד. אושר ע"י חיים דרך שער Claude Design (mockup 09-missing-precedents).

- db.list_missing_precedents + get_missing_precedent: שדות-provenance מחושבים
  (_MP_PROVENANCE_COLS משותף, G2): cited_by_precedents (array_agg מ-
  precedent_internal_citations עבור cited_only — מי-מצטט את ה-stub) +
  yomon_number (substring מ-notes עבור digest). discovery_source כבר הוחזר.
- web-ui: MissingPrecedent type + תא "צוטט ע"י" מסתעף לפי discovery_source:
  cited_only→chip "פסיקה בקורפוס" + "מצוטט ע"י: <מספרים>"; digest→chip "יומון" +
  "מס' X"; manual→SourceChip+צד (כמו היום). טוקני plum/teal ב-globals.css
  (מה-mockup המאושר).

אומת מול ה-DB החי: cited_only→מצטטים (רע"א 1054/21→8047-23,8126-03-25),
digest→מס'-יומון (306 רשומות). tsc נקי, eslint נקי, 360 בדיקות mcp עוברות.

Invariants: G2 (שדה-provenance יחיד משותף ל-list+detail), G1 (נגזר במקור),
INV-IA*/UI (שינוי-עמוד דרך שער Claude Design), G12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 07:37:29 +00:00
9fd506ff2b feat(digests): יומון-לא-מקושר → "פסיקה חסרה" — סוף לבליעה-שקטה (#136)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
צינור-היומונים (X12) קישר אוטומטית רק לפסיקה שכבר בקורפוס; ציטוט שלא נמצא נבלע
בשקט אלא אם היה פס"ד בית-משפט בר-אחזור. כך 369 עררים + 21 לא-מסווגים שהוזכרו
ביומונים מעולם לא הופיעו כפער.

תיקון (G2 — מסווג יחיד + נתיב-MP יחיד; INV-DIG3/CF2 — אין בליעה-שקטה):
- digest_library: ה-gap branch (try_autolink ללא-התאמה) קורא כעת
  _handle_unlinked_citation — ניתוב דרך court_citation.classify: supreme/admin →
  court_fetch_job (כקודם; האורקסטרטור פותח MP משלו בכשל), skip(ערר/בל"מ)/unknown →
  missing_precedent (discovery_source='digest', provenance=מס'-יומון+digest_id),
  deduped designator-aware דרך citation_norm (#143).
- court_fetch_orchestrator._open_gap: הוקשח ל-dedup אמיתי (find לפני create) +
  discovery_source='court_fetch' — התגובה הבטיחה "deduped" אך create לא דידאפ.
- scripts/backfill_digest_missing_precedents.py: מריץ try_autolink על 461 הקיימים
  (dry-run: 71 fetchable + 390 gap). אידמפוטנטי. יורץ אחרי הפריסה.

תלוי-הקשר #143 (citation_norm + נתיב-יצירה). השפעת-UI: דף "פסיקה חסרה" יגדל
מ-207 ל-~597 פתוחים (אושר ע"י חיים).

בדיקות: test_digest_unlinked_citation (ערר→MP, פס"ד→fetch, dedup, unknown→MP).
כל 360 עוברות. guards נקיים.

Invariants: G2 (מסווג+נתיב-MP יחיד), INV-DIG3/INV-CF2 (פער גלוי, לא נבלע),
INV-DIG1 (יומון מצביע, לא מצוטט), G1 (dedup מנורמל), G12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:06:12 +00:00
161e370a4c feat(precedents): איחוד cited_only↔missing_precedents — גזירת פסיקה-חסרה (#143, G2)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
שתי מערכות מקבילות לאותו מושג ("פסיקה מצוטטת שטקסטה לא נקלט"): טבלת
missing_precedents (תור-רכישה ידני של היו"ר) מול case_law source_kind='cited_only'
(stubs מגרף-הציטוטים/X11). חפיפה≈0 → 31 ה-stubs לא הופיעו ב-/missing-precedents.

הכרעה (G2): missing_precedents = SoT-לתור-יחיד; cited_only = מקור-גילוי נגזר (כמו
יומונים מזינים radar). גוזרים רשומת missing_precedents 'open' לכל stub.

תיקון:
- court_citation.citation_dedup_key — מפתח-dedup **designator-aware**
  (`{designator}|{docket}`). **מתקן פגם בתוכנית-הניתוח:** dedup על מספר-בלבד היה
  ממזג בטעות אותו docket בערכאות שונות (בג"ץ 389/87 ≠ ע"א 389/87; 18 כאלה בקיים).
- סכמה V40: missing_precedents מקבל citation_norm (מפתח-dedup) + discovery_source
  (manual|cited_only|digest|writer) + index. **בלי UNIQUE** — הקורפוס מחזיק
  לגיטימית אותו docket בערכאות שונות; ייחודיות נאכפת designator-aware בנתיב-היצירה.
- create_missing_precedent: מחשב citation_norm בכתיבה (G1), מקבל discovery_source
  + linked_case_law_id. find_missing_precedent_by_citation: dedup דרך citation_norm
  (fallback ל-citation גולמי כשאין מספר).
- scripts/derive_missing_from_cited_only.py: backfill citation_norm ל-291 +
  גזירת 31 (dry-run: 31 ייווצרו, 0 deduped). linked_case_law_id=stub, status=open
  → promote-in-place בהעלאת-טקסט דרך ON CONFLICT הקיים. אידמפוטנטי.

תלוי-הקשר: #140 (הגדרת cited_only). מתואם עם #136 (digest→MP — אותו citation_norm
+ create path). תיקון-נתון יורץ אחרי הפריסה.

בדיקות: test_dedup_key_is_designator_aware (בג"ץ≠ע"א, ערר≠בל"מ, גרסאות-format
מתמזגות). כל 356 עוברות. guards נקיים.

Invariants: G2 (SoT-לתור יחיד, cited_only נגזר), G1 (citation_norm מנורמל בכתיבה),
G3 (idempotent upsert), G10 (שער-העלאה ידני נשמר), G12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 08:56:34 +00:00
bea2065640 refactor(court-fetch): usage_status קורא ממקור-האמת המשותף usage_limits (איחוד ההעתק השלישי) (#268)
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m38s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 11s
Co-authored-by: Chaim <chaim@marcus-law.co.il>
Co-committed-by: Chaim <chaim@marcus-law.co.il>
2026-06-15 04:25:11 +00:00
6cc100f9f8 fix(halacha): rate-limit refusal ≠ empty answer — לא checkpoint chunk בכשל (#144)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
תיקון-ליבה (b): כש-claude CLI מחזיר exit=0 עם הודעת-מגבלה/שגיאה כ-result, query
זיהה אותה כהצלחה → _extract_chunk קיבל []/non-list וסימן chunk כ-done-ריק; resume
דילג עליו לתמיד → תת-חילוץ קבוע (3→1→0). עכשיו is_error/_looks_like_limit_notice
הופכים אותה לכשל-חולף → retry → raise → chunk נשאר un-checkpointed → resume משחזר
(כך force-delete כבר לא הרסני-לצמיתות).

+ churn-detect במתזמר (Δdone<0 / Δhal<-2 → אזהרה+churn_ok ב-JSON).
+ scripts/reconcile_under_extracted_halacha.py — שחזור completed-עם-0-הלכות-ו≥3
  מקטעי-נימוק (dry-run הראה 15 מועמדים); נתיב-הזמנה קנוני (G2), שמרני (לא remand).

הערה: אטומיות-מלאה (staging_run_id) נדחתה — PR #257 מיתן את ה-trigger, ו-(b)+resume
מונעים אובדן-קבוע (force-delete מתאושש דרך resume).

בדיקות: test_claude_session_limit_notice. כל 354 עוברות. guards נקיים.
Invariants: G1, INV-G3/X16 (checkpoint=הושלם-באמת), INV-G4 (churn לא-שקט), G12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 04:21:15 +00:00
9e45e5a46d Merge pull request 'fix(precedents): נרמול case_number עמיד-להתנגשות — מדלג ומתעד, לא קורס (#145)' (#266) from worktree-backfill-citations-run into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m30s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 12s
2026-06-15 04:17:38 +00:00
a02b929b5c fix(precedents): נרמול case_number עמיד-להתנגשות — מדלג ומתעד, לא קורס (#145)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
ה-backfill של citation_formatted חשף קריסה ב-apply_to_record: כשפסק-דין
חיצוני מכיל docket שכבר שייך לרשומה כפולה אחרת, נרמול case_number → docket-נקי
נתקל ב-uq_case_law_external_number ומפיל את כל המיזוג (כולל הציטוט).
דוגמה: 'ע"א 3213/97' → '3213/97' שכבר קיים (כפילות נקר).

- db.case_number_collides(case_number, exclude_id) — בודק אם docket כבר שייך
  לרשומה לא-internal אחרת (האינדקס החלקי).
- apply_to_record — מדלג על נרמול ה-case_number כשיש התנגשות (כפילות לדדופ
  בהמשך, לא ענייננו כאן) וממשיך לכתוב את הציטוט. no-silent-swallow: מתעד warning.
- scripts/backfill_precedent_citations.py — try/except per-row + מונה שגיאות,
  כך ששורה אחת לא מפילה את האצווה.

אומת: ריצה-מחדש מלאה ללא קריסה (0 שגיאות); ההתנגשות תועדה ודולגה כצפוי;
פסיקת בית-משפט: 224/228 מולאו, 4 נמנעו (חסר צדדים/תאריך — abstention, INV-AH).
test_fu2b_reconcile ✓.

Invariants: INV-AH (abstention) · G1 (נרמול-בכתיבה נשמר, רק לא קורס) ·
חוקה §6 (אין בליעה שקטה — דילוג מתועד).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 04:17:07 +00:00
07ecb6a366 feat(halacha): עצירה-רכה של הדריינר בסף-ניצול (75/65) + מקור-אמת יחיד למכסה (#265)
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m29s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 11s
Co-authored-by: Chaim <chaim@marcus-law.co.il>
Co-committed-by: Chaim <chaim@marcus-law.co.il>
2026-06-15 04:11:43 +00:00
2343892220 fix(extraction): reconcile לתיקים-יתומים בתור — pending+requested_at=NULL (#139)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
תיק יכול להיות <kind>_extraction_status='pending' עם _requested_at=NULL —
מעולם-לא-נכנס-לתור (מסלולי bulk/מיגרציה, או status שנכתב לפני החותם), והדריינר
(סורק requested_at IS NOT NULL) עיוור אליו לנצח → ה-backlog מתנקז בשקט לאפס.
requeue_stale_processing_extractions מרפא רק 'processing'. נצפה 2026-06-14:
96 תיקים pending אך 0 בתור (תוקנו ידנית).

תיקון (G1 — שחזור invariant במקור, G2 — predicate יחיד):
- db.reconcile_orphaned_pending_extractions(kind=) — kind-agnostic, מחזיר את
  invariant "שורה ברת-חילוץ ⇒ בתור": חותם requested_at ל-rows שהם pending +
  requested_at IS NULL + EXTRACTION_ELIGIBLE_PREDICATE (אותו מסנן של #140 —
  cited_only/chunkless לעולם לא נדחפים). אידמפוטנטי (rows מסומנים לא נתפסים).
- precedent_library.process_pending_extractions קורא reconcile אחרי requeue_stale
  ולפני list — תיקים-משוחזרים נקלטים באותו pass. מנגנון-ריפוי יחיד (G2), לא מסלול
  מקביל; requeue_stale='processing', reconcile='pending'.
- request_halacha_extraction מציב status='pending' עם החותם (סימטרי ל-metadata)
  — סוגר את חלון-ה-drift שמייצר pending+NULL מלכתחילה.

מצב חי נקי (0 יתומים-כשירים אחרי התיקון-הידני); זהו תיקון מונע — הדריינר יְרַפֵּא
יתומים עתידיים אוטומטית.

בדיקות: test_extraction_orphan_reconcile (predicate משותף, pending+NULL בלבד,
מובחן מ-requeue_stale, request_halacha סימטרי), שני ה-kinds. כל 349 עוברות.

Invariants: G1, G2 (predicate משותף עם #140, ריפוי יחיד), INV-G3/INV-DUR1 (X16),
INV-G4 (אין בליעה שקטה — reconcile מתעד), G12.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 04:09:54 +00:00
c348903e4b fix(extraction): סינון cited_only מתור/מוני החילוץ (#140)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 7s
Lint — undefined names / undefined-names (pull_request) Successful in 14s
31 שורות case_law עם source_kind='cited_only' (ציטוט-בלבד, ללא full_text/chunks)
נושאות halacha_extraction_status='pending' רק כברירת-מחדל ומזהמות את מונה ה-pending
ובמתזמר/בדף-התפעול — אין להן מה לחלץ.

תיקון (G1 — תיקון-במקור, G2 — מסנן יחיד משותף):
- db.EXTRACTION_ELIGIBLE_PREDICATE — מקור-אמת יחיד ל"שורה ברת-חילוץ" (source_kind
  <> 'cited_only' AND יש precedent_chunks). מוחל ב-list_pending_extraction_requests;
  #139 יעשה בו שימוש-חוזר ל-reconcile (אותו כלל, לא כפול).
- מוני-snapshot מסננים cited_only: halacha_drain_supervisor.db_snapshot,
  web/app.py meta+hal_ext (GROUP BY status).
- reconcile_metadata_status.py מורחב לכסות גם את תור-ההלכות: cited_only→'skipped'
  (אותו terminal-state כמו צד-המטא, תור-תאום, G2). בוצע על ה-DB החי: 31 הועברו
  ל-'skipped' (metadata כבר היה מיושב — אידמפוטנטי). התפלגות-אחרי: halacha
  pending=9 (עבודה אמיתית), skipped=31, completed=309.

בדיקות: test_extraction_queue_eligibility (predicate + list_pending מחיל אותו,
שני ה-kinds). כל 345 בדיקות mcp עוברות. guards נקיים.

Invariants: G1 (terminal-state אמיתי במקור), G2 (predicate יחיד, ללא תור מקביל),
INV-DM1 (stub לא-searchable אינו מועמד-חילוץ), G12 (leak-guard נקי).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 04:03:21 +00:00
7043de0ac2 Merge pull request 'feat(precedents): citation_formatted דטרמיניסטי בקוד — Gemini מחלץ רכיבים, לא מעצב (#145)' (#262) from worktree-precedent-deterministic-citation into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m47s
G12 Leak-Guard / leak-guard (push) Successful in 8s
Lint — undefined names / undefined-names (push) Successful in 22s
2026-06-15 03:38:51 +00:00
d6608ce849 feat(precedents): citation_formatted דטרמיניסטי בקוד — Gemini מחלץ רכיבים, לא מעצב (#145)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
הבעיה (#145): מחלץ-המטא ביקש מ-Gemini Flash *לעצב* את מראה-המקום המלא
(citation_formatted). ב-JSON-mode חופשי (ללא responseSchema) המודל החזיר JSON
תקין ומלא אך **השמיט בעקביות** דווקא את השדה הזה — אומת על 8070-05-25,
1194-12-25, 1200-12-25 (וגם כשהצדדים זוהו). השדה הקשה ביותר (עיצוב מחרוזת) +
היתר-בפרומפט להשאיר ריק → Flash מפיל אותו.

הפתרון: citation_formatted הוא **שדה-תצוגה נגזר** (X1 §3 / INV-ID2) — מורכב
דטרמיניסטית מרכיבים מובְנים, לא מעוצב ע"י LLM. תפקיד ה-LLM מצטמצם לחילוץ
רכיבים אמינים (שורת-הצדדים, קידומת-ההליך לפסקי-בית-משפט).

- db.format_precedent_citation(record) — מרכיב לפי כללי-הציטוט-האחיד:
  ועדת-ערר (מחוזית/ארצית/בל"מ) מ-proceeding_type+district+source_kind;
  פסקי-בית-משפט מ-court_prefix(LLM)+district-abbrev. מוציא docket נקי
  מ-case_number מזוהם ("עע\"מ 683/13"→"683/13"). נמנע ('') כשחסר רכיב
  (צדדים/docket/תאריך/קידומת) — abstention על המצאה (INV-AH).
- case_law.parties (V39) — שורת "עורר נ' משיב" כבסיס re-derivable.
- מחלץ-המטא: הפרומפט מחלץ parties+citation_prefix (לא citation_formatted);
  apply_to_record מרכיב דטרמיניסטית מהרשומה-האפקטיבית וממלא רק שדה ריק
  (עריכות-יו"ר נשמרות).
- scripts/backfill_precedent_citations.py — backfill 2-מעברים (דטרמיניסטי→LLM),
  מדווח שורות-נמנעות, idempotent.

אומת: 3 הרשומות הידניות משוחזרות תו-בתו; פסק עליון אמיתי מולא end-to-end
(עע"מ 683/13 ... נבו 3.9.2015). test_fu2b_reconcile ✓.

Invariants: INV-ID2/X1§3 (ציטוט=תצוגה נגזר, לא מפתח) · INV-AH (abstention,
אפס המצאה) · G1 (docket נקי) · G2 (מסלול-יחיד — מחליף את נתיב-ה-LLM, לא מקביל).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 03:37:53 +00:00
33b07eebcf Merge pull request 'fix(metadata): לא להתיישב 'completed' בכשל-חילוץ-Gemini חולף (#138)' (#261) from worktree-metadata-no-settle-on-fail into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m42s
G12 Leak-Guard / leak-guard (push) Successful in 6s
Lint — undefined names / undefined-names (push) Successful in 11s
2026-06-15 03:35:47 +00:00
77817a46ad fix(metadata): לא להתיישב 'completed' בכשל-חילוץ-Gemini חולף (#138)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 7s
Lint — undefined names / undefined-names (pull_request) Successful in 17s
צד-המטא-דאטה (precedent_metadata_extractor) קרא רק GEMINI_API_KEY בעוד בסביבה
קיים GOOGLE_GEMINI_API_KEY — תוקן ב-PR #255 (fallback). הבאג המשני שנותר: כש-
extract_and_apply החזיר 'no_metadata' (כשל-Gemini), מסלול-הדריינר
process_pending_extractions התיישב ל-metadata_status='completed' ללא-תנאי, כך
שהרשומה ננטשה בשקט עם מטא ריק והדריינר לא חזר אליה (נצפה: da2d9ccb '4491-02-21',
5fabdac5 '14306-09-23' — completed אך court/date/summary ריקים).

תיקון (G1 — אבחנת-מקור):
- extract_and_apply מבדיל תוצאה-ריקה: יש full_text → 'extraction_failed' (חולף,
  בר-retry); אין full_text → 'no_metadata' (אין מה לחלץ).
- process_pending_extractions (metadata): 'extraction_failed' → חוזר ל-'pending'
  (משמר את חותם-התור) במקום להתיישב 'completed'. retry-loop הקיים מנסה שוב,
  ואחרי-מיצוי הרשומה נשארת בתור. מסלול reextract_metadata כבר עקבי (חוזר pending
  על כל מה שאינו completed/no_changes).

תיקון-נתון (בוצע ידנית דרך כלי-MCP precedent_extract_metadata): da2d9ccb +
5fabdac5 חולצו-מחדש בהצלחה (court/date/summary/headnote/tags מלאים). 0 נותרו
external 'completed-but-empty'.

הערה: מפתח-Gemini אינו נדרש ב-Coolify — המחלץ רץ רק מקומית (precedent_library →
extract_and_apply, host ~/.env עם GOOGLE_GEMINI_API_KEY); app.py מייבא רק את
הקבוע PLACEHOLDER_PENDING_EXTRACTION, לא את פונקציית-החילוץ.

בדיקות: test_metadata_extract_failure_status (transient/permanent/missing). כל
335 בדיקות mcp עוברות. guards נקיים.

Invariants: G1 (אבחנת-מקור, לא התיישבות-בקריאה), INV-G3/X16 (עמידות — בר-retry),
G12 (leak-guard נקי).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 03:34:43 +00:00
c27987ba72 fix(precedents): חילוץ מספר-תיק קנוני מהציטוט — לא ציטוט-מלא כמזהה (#137)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 14s
בהעלאה דרך "פסיקה-חסרה" (ענף ועדת-ערר), כשטופס case_number ריק המסלול נפל-לאחור
לציטוט המלא (committee_case_number = case_number.strip() or citation), כך שמחרוזת-
תצוגה עם שמות-צדדים הושתלה בשדה-המזהה — הפרת INV-ID2/INV-ID1 (X1). נצפה על
precedent 1bf0bae0 (ערר 85074-04-25 רפאל לוי/חולון): case_number=85074/0425,
case_name=ציטוט שלם.

תיקון (G1 — נרמול-במקור, G2 — שימוש-חוזר בפרסר הקנוני):
- court_citation.case_number_from_citation(citation) — מחזיר את אסימון-המספר
  המנורמל בלבד (classify; '' כשאין מספר). חולץ נכון 85074-04-25 גם מתוך
  "ערר (ת\"א 85074-04-25) ...". reuse של הפרסר היחיד, בלי regex מקביל.
- web/app.py (ענף ועדת-ערר): fallback דרך case_number_from_citation; אם אין
  מספר — HTTPException 400 "נא להזין מספר-תיק ידנית" במקום השתלת ציטוט-מלא.
- db._canonical_case_number: מוקשח לחלץ את אסימון-המספר (זורק זנב שמות-צדדים),
  כך ששדה-המזהה לעולם לא נשמר מזוהם — גם בקריאה ישירה (committee + active cases).
  מספר נקי חוזר ללא שינוי; חודש לא מומצא (X1 §1).
- תיקון-נתון: scripts/fix_137_committee_case_number.py (בוצע) — 1bf0bae0:
  case_number→85074-04-25, case_name→צדדים, token ב-citation_formatted.
  אומת היחיד עם canon(num)≠num ב-internal_committee. אידמפוטנטי.

מחוץ-לתחום (תועד כ-follow-up): מסלול external (precedent_library) משתמש בציטוט-
מלא כמזהה-מורשת — זהו פריט-המיגרציה X1 §5 (138 רשומות external/cited_only),
לא הבאג הזה. prefill ב-UI של /missing-precedents — דורש שער Claude Design.

בדיקות: test_court_citation (case_number_from_citation: party-strip/forms/empty),
test_canonical_case_number (harden). כל 339 בדיקות mcp עוברות. guards נקיים.

Invariants: G1 (נרמול-במקור), INV-ID1/ID2 (מזהה מנורמל, אין ציטוט-מלא כמזהה),
G2 (פרסר יחיד), G12 (leak-guard נקי).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 03:21:10 +00:00
eb86e475c3 fix(halacha): import asyncpg — undefined-name guard (annotation ב-_acquire_global_lock)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
Lint — undefined names / undefined-names (pull_request) Successful in 14s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 02:56:49 +00:00
b7ffc0387c fix(halacha): שחזור-עצמי לנעילת-advisory דלופה — לא לחסום חילוץ-הלכות (#142)
Some checks failed
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Failing after 13s
כשה-legal-halacha-drain קרס עם "RuntimeError: Event loop is closed", ה-finally
שמריץ pg_advisory_unlock + pool.release לא רץ, וחיבור-הנעילה הייעודי נשאר חי,
idle, מחזיק את הנעילה הגלובלית — כל extract עתידי החזיר status='busy' לצמיתות
עד pg_terminate_backend ידני (~4.5 דק', CMP-174, 2026-06-14).

תיקון (G1 — נרמול-במקור, G2 — אותה נעילה, בלי מסלול מקביל):
- KEEPALIVE: משימת-רקע נוגעת בחיבור-הנעילה כל 30ש' → state_change נשאר טרי.
  חילוץ חי לעולם לא נראה "תקוע"; קריסה מקפיאה את ה-keepalive ואת state_change.
- שחזור-עצמי בכניסה (_acquire_global_lock): כש-pg_try_advisory_lock נכשל, בודקים
  את ה-holder; רק backend idle עם state_change ישן מ-_LOCK_STALE_AFTER (150ש',
  5× keepalive) הוא orphan דלוף → pg_terminate_backend ואז acquire מחדש.
  backend 'active' או idle-טרי = חילוץ חי, לעולם לא נהרג (מניעת ה-box-freeze).
- נדחתה אופציית pg_advisory_xact_lock: הייתה כופה transaction פתוח לאורך דקות
  (idle-in-transaction bloat) ועדיין לא משחררת מיידית חיבור-orphan חי.

הערה: השתמשתי במונח DB-סטנדרטי "keepalive" (לא "heartbeat") כי leak_guard מסמן
את "heartbeat" כסמל ספציפי-Paperclip (G12).

בדיקות: tests/test_halacha_lock_selfheal.py (7) — free/live-holder/active-holder/
stale-orphan-reclaim/no-holder/keepalive-stop/extract-busy. כל 332 בדיקות mcp עוברות.

Invariants: G1 (תיקון-במקור), G2 (אותה נעילה), G3/X16 (עמידות-פייפליין), G12 (leak-guard נקי).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 02:54:35 +00:00
a05df3eb1a fix(precedents): normalize citation→docket case_number + enforce source_type↔precedent_level
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 12s
שני באגים בקליטת-פסיקה חיצונית (התגלו בתיק 1132-09-24 שהועלה דרך "פסקה חסרה"):

1. case_number קיבל את מחרוזת-הציטוט המלאה במקום דוקט נקי. הסיבה: overwrite_case_number=True
   הועבר רק לנתיב-הפנימי (internal_decisions); נתיב-הדריינר ל-external השאיר את הציטוט שב-
   case_number (precedent_library: case_number=citation). היקף: 122 רשומות external_upload.
2. source_type לא נאכף מול precedent_level — רק ה-prompt ביקש מה-LLM. כשה-LLM פלט
   level=ועדת_ערר_מחוזית אך source_type=court_ruling, ההחלטה סווגה בספרייה כ"פסיקת בית משפט".

תיקון (ב-apply_to_record, כך שכל הנתיבים נהנים):
• case_number מנורמל לדוקט הנקי כש-(א) caller כופה או (ב) הערך הנוכחי ציטוט-צורני (רווח/אורך>20);
  guard _is_clean_docket מבטיח שלעולם לא נכתב ערך לא-דוקט לשדה-הזהות (LLM-זבל נדחה).
• _source_type_for_level גוזר source_type מ-precedent_level ודורס אי-עקביות (ועדת_ערר_*→
  appeals_committee; עליון/מנהלי→court_ruling) — מקור-אמת אחד, לא הישענות על עקביות-LLM.

נבדק: 18 unit-tests (docket-validation, level→type mapping) + 3 integration-tests מול
apply_to_record עם DB מדומה (נרמול, אי-דריסת-דוקט-תקין, דחיית-זבל, אכיפת-עקביות). py_compile נקי.
תיקון-נקודתי כבר בוצע ידנית ל-1132-09-24. Backfill ל-122 בנפרד (TaskMaster #141).

Invariants: G1 (תיקון-במקור), G2 (אותו extractor — בלי מסלול מקביל), INV-AH (מקור-אמת
דטרמיניסטי לסיווג, לא ניחוש-LLM). G11 (זהות-תיק נקייה).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:57:08 +00:00
a40c4ee828 fix(metadata): accept GOOGLE_GEMINI_API_KEY (canonical) in gemini_session — host metadata extraction broke
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
_api_key() read ONLY `GEMINI_API_KEY`, but the canonical secret (host ~/.env and
Infisical SoT nautilus:/external-apis/gemini) is `GOOGLE_GEMINI_API_KEY`. The key
was present but under the canonical name → `_api_key()` raised "GEMINI_API_KEY אינו
מוגדר" on every call → ALL host precedent-metadata extraction via Gemini failed
silently (186 such errors in the legal-metadata-drain err log, latest 2026-06-14).

Fix: read GEMINI_API_KEY if set, else fall back to GOOGLE_GEMINI_API_KEY. No new
secret, no duplication — aligns the code to the existing SoT name (G1: fix at
source). Verified live: _api_key() resolves (len=53) and a real gemini query_json
call returns {"ok": true}.

Invariants: G1 (fix at source — code reads the canonical secret name, not a
parallel/duplicated env var) · X10 (deploy-env-secrets: single SoT name honored).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 20:33:15 +00:00
46bcaa8fa3 feat(plans): הרשאות-סוכנים + dedup/merge/edit + API לתור-אישור תכניות (backend)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
Follow-ups למרשם-התכניות (PR #252), חלק ה-backend (ללא-UI):
- סוכנים: הוספת extract_plans/plan_get/plan_search/plan_list ל-CEO ול-חוקר
  (+ plan_upsert לחוקר); plan_review נשאר אנושי בלבד (G10). .claude/agents/*.md.
- db: _plan_core_token + find_similar_plans (הצפת כפילות-וריאנט לאישור ידני, בלי
  מיזוג-אוטומטי), update_plan (עריכה+renumber, guard התנגשות→merge), merge_plans
  (איחוד aliases, מילוי-חוסר, סתירות→discrepancies, מחיקת מקור).
- plans_extractor: צירוף possible_duplicates לפלט החילוץ.
- web/app.py: GET /api/plans(+/{id},/{id}/duplicates) · POST /api/plans · PATCH
  /api/plans/{id} · POST /api/plans/{id}/review · POST /api/plans/merge; +קטגוריית
  "תכניות הממתינות לאישור" ב-/api/chair/pending.

תיקון-נתונים (DB, מחוץ ל-PR): הל/מח/250 ד' → 7.1.2002, י"פ 5045.
ה-UI (טאב /precedents + מונה /approvals) ב-PR נפרד אחרי שער Claude Design.

Invariants: G1 (נרמול בכתיבה/עריכה) · G2 · G3 · G10 (review_status + מיזוג ידני,
אישור אנושי) · INV-DM2/DM5 · INV-AH · X9 · אין בליעה שקטה (discrepancies).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 14:39:46 +00:00
4be9cf8543 feat(plans): מרשם-תכניות קנוני (V38) + נוסח-ציטוט אחיד דטרמיניסטי לבלוק ט
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
מוסיף ישות קנונית לתכניות בניין-עיר (תב"ע) שחוזרות בין תיקים — SSOT לזהות+תוקף
(פרסום למתן תוקף ברשומות + מס' ילקוט-הפרסומים) + משפט-ייעוד — במקום גזירה-מחדש
מהשומות בכל תיק. בלוק ט מצטט את התוקף בנוסח אחיד דטרמיניסטי (format_plan_citation),
כך שתאריך-פרסום/מס'-ילקוט לעולם לא מהוזים ע"י ה-LLM.

- DB: טבלת plans (V38) + CRUD + _normalize_plan_number (G1) + format_plan_citation;
  upsert idempotent (G3) עם כלל-מיזוג: תוקף מאושר לא נדרס — סתירה נרשמת ב-discrepancies
  (G10 / אין בליעה שקטה).
- services/plans_extractor.py: חילוץ עובדתי (claude CLI מקומי) → pending_review.
- block_writer.py: _build_plans_registry_context מזריק משפטי-ציטוט מאושרים בלבד לבלוק ט;
  תכניות חסרות/לא-מאושרות מסומנות במפורש (לא נבלעות).
- tools/plans.py + server.py: extract_plans / plan_get / plan_search / plan_list /
  plan_upsert / plan_review (שער-יו"ר G10), עם extract/get-symmetry (X9).
- scripts/backfill_plans_registry.py: ייבוא מקורפוס-ההחלטות (טיוטות + סופיי-דפנה).
- docs: block-schema (בלוק ט), SKILL, spec 02-data-model + 04.

Invariants: G1/INV-DM2/X1 (מזהה מנורמל בכתיבה) · G2/INV-DM6 (מקור-אמת יחיד, appraiser_facts
ללא שינוי) · G3 (upsert) · INV-DM4/G9 (provenance) · INV-DM5/G10 (review_status) ·
INV-AH (ציטוט דטרמיניסטי) · G5 (lookup לא קורפוס) · G11/block-schema (נוסח-הציטוט) · X9.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 13:46:26 +00:00
0a3bc35623 ci: gate undefined names (pyflakes F821) + fix latent NameError in db.py
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
Prevents recurrence of the case-rename 500 (PR #249), whose root cause was
an undefined name (`paperclip_client`) sitting in a background_tasks callable
— invisible until that code path ran in production.

- scripts/check_undefined_names.py: runs pyflakes on web/, mcp-server/src,
  scripts/ and fails ONLY on "undefined name" / "may be undefined" (the
  runtime-crash class). Unused imports / f-strings are NOT gated — keeps the
  check high-signal and green.
- .gitea/workflows/lint.yaml: runs the guard on every PR and push to main,
  in a throwaway venv (PEP-668 safe).
- db.py: `from datetime import date` → `date, datetime`. The guard surfaced a
  real latent undefined name — `insert_panel_round`'s `round_ts: datetime`
  annotation referenced an unimported `datetime` (benign only because of
  `from __future__ import annotations`; now correct).
- SCRIPTS.md: documented the new guard.

Verified: clean tree → exit 0; injected undefined name → exit 1.

Invariants: engineering rule §6 (no silent failures shipping to runtime).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 09:58:45 +00:00
64612240d5 feat(ops): פאנל "מתאמי-סוכנים" ב-/operations — מעבר-אדפטר בכפתור (any→any)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
שלב-ה-UI של מנגנון מעבר-האדפטר (PR #247). הכפתור ב-/operations מריץ את
scripts/migrate_agent_adapter.py בהוסט דרך גשר-court-fetch (הקונטיינר לא יכול
לבצע — צריך FS-הוסט + DB-המובנה), בדיוק כמו כפתורי-pm2.

- court_fetch_service/server.py: endpoint /adapter-migration מאומת (Bearer,
  COURT_FETCH_SHARED_SECRET) שמריץ את הסקריפט עם allowlist-פעולות ו-args אטומיים
  (exec, ללא shell; הסקריפט מאמת). symbol-light לשמירת G12 בשכבת mcp-server.
- web/app.py: proxy POST /api/operations/agents/migrate-adapter → הגשר.
- web-ui: useMigrateAdapter (operations.ts) + AgentAdaptersPanel לפי המוקאפ
  המאושר 02d-operations-adapters.html: roster per-role (מתאם נוכחי+מודל+בורר-יעד),
  סרגל-חירום גלובלי (הכל→Gemini/החזר→Claude), preflight בדיאלוג + toggle שחרור-כלים,
  דגלי מועבר/א-סימטרי. מחזר usePaperclipAgents לתצוגה.

עיצוב אושר ע"י חיים בשער Claude Design (פרויקט IA Redesign X17).
נבדק: tsc --noEmit נקי, eslint נקי.

Invariants: G12 (גשר symbol-light; הסקריפט בשכבת scripts/ הוא שמדבר Paperclip),
INV-MC1 (שתי החברות יחד), INV-IA "מקום אחד" (/operations). המשך FU-8a.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 12:06:40 +00:00
693126484b fix(operations): cache the usage endpoint (avoid 429) + show weekly-Sonnet not Opus
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Two follow-ups to the usage-% feature:

1. The /api/oauth/usage endpoint 429s when polled often — /operations refreshes
   every 5s and each refresh hit it (plus the supervisor + ad-hoc calls). Cache
   the last good payload on the host bridge for 60s and serve it; only re-fetch
   when stale, so Anthropic sees ~1 req/min regardless of dashboard polling. On
   a fetch failure (e.g. a transient 429) serve the last good payload instead of
   blanking the card. The 5-hour window moves slowly, so 60s stays fresh.

2. The third meter showed weekly-Opus, which is null on this account (the
   per-model weekly cap that's actually populated is Sonnet). Switched the
   display to seven_day_sonnet / "שבועי · Sonnet". (The supervisor keeps gating
   on seven_day_opus — the halacha drain runs Opus, so the Opus cap is the
   correct gate even when it's null/inactive.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 10:43:57 +00:00
6f3c3963a4 feat(operations): show real claude.ai subscription usage % on /operations
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Surfaces the 5-hour / weekly / weekly-Opus utilization the Claude Code status
bar shows — the authoritative number, not a token estimate. Design approved via
the Claude Design gate (card 02c-operations-usage.html).

Three layers:
- court-fetch-service (host bridge): new GET /usage reads the OAuth token from
  ~/.claude/.credentials.json and proxies /api/oauth/usage with the required
  claude-code User-Agent. Read-only, no auth (like /pm2). Host-only — the token
  never enters the container.
- web/app.py: _ops_subscription_usage() proxies the bridge /usage; the
  /api/operations snapshot gains a `subscription_usage` field (null when the
  undocumented endpoint is unreachable).
- web-ui: SubscriptionUsagePanel renders three meters (label · % · bar · reset)
  at the top of /operations; bar turns amber >75% / red >90%; hidden when usage
  is null. Types added to operations.ts (hand-maintained snapshot type).

Also fixes a pre-existing react/no-unescaped-entities lint error in
learning-panel.tsx (escaped a `"` in Hebrew text — renders identically).

tsc --noEmit passes; lint error count 0. (Full next build is blocked only by the
manual-worktree node_modules symlink — the Docker build has real node_modules.)

Invariants: G2 (usage surfaced through the existing host bridge + /api/operations
snapshot — no parallel control path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 10:34:50 +00:00
49acde591e fix(db): serialise schema migrations with an advisory lock + stagger drain crons
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
legal-halacha-drain crashed 29× with asyncpg DeadlockDetectedError. Root cause:
every short-lived cron drain re-runs the idempotent schema migrations on startup
(get_pool → _run_schema_migrations), and three jobs (metadata-drain, halacha-drain,
halacha-supervisor) all fired on the same minute (*/15 / top-of-hour). Two
processes running the DDL concurrently took AccessExclusiveLock in opposite order
→ Postgres killed one with a deadlock.

Two-layer fix:
- Root cause: wrap _run_schema_migrations in a session-level pg_advisory_lock so
  only one process applies DDL at a time; concurrent migrators wait instead of
  deadlocking. DDL body extracted to _apply_schema_ddl. Idempotent, schema
  unchanged.
- Defence-in-depth: give each cron drain a distinct firing minute —
  metadata :00, supervisor :05, halacha-drain :10, digest :12, court-fetch :17 —
  so siblings no longer start at the same instant. SCRIPTS.md updated to match.

Invariants: G1 (fix at source — the single migration path — not the symptom);
G2 (no parallel control path introduced).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 08:08:19 +00:00
15e4af595a fix(storage): ASCII-encode S3 object metadata — s3-only upload 500 on Hebrew filenames (INV-STG2)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 7s
תיקון: כל העלאת קובץ עם שם עברי נכשלה ב-500 תחת backend s3-only. השורש:
`ingest._stage_file` מצרף את שם-הקובץ המקורי כ-S3 object metadata
(`metadata={"filename": src.name}`), ו-`S3Backend.put_bytes` העביר אותו כמו-שהוא
ל-`put_object`. botocore אוכף ASCII-only על S3 metadata → ParamValidationError →
500. שם עברי כמו "יומון 5167 - 11.6.26.pdf" שבר כל upload. נחשף ב-cutover ל-s3-only
(2026-06-11): קליטת היומונים (וגם כל מסמך/פסיקה עם שם עברי) הפסיקה לעבוד; היומון
האחרון שנקלט (5165, 9.6) היה לפני ה-cutover.

התיקון (נרמול-במקור, G1; בשכבת-האחסון היחידה, INV-STG2):
- `_ascii_metadata` מקודד ערכי-metadata לא-ASCII ב-percent-encoding (lossless,
  שחזור עם urllib.parse.unquote); ASCII רגיל עובר ללא שינוי (קריאוּת).
- `S3Backend.put_bytes` מחיל אותו על כל ערכי ה-Metadata.

בדיקות: test_ascii_metadata_encodes_hebrew (helper) +
test_s3_put_bytes_sends_ascii_metadata (משחזר את מסלול-הכשל מול fake put_object).
16 עוברות בקובץ.

Invariants: מקיים G1 (נרמול-במקור, לא תיקון-בקריאה), INV-STG2 (שם-קובץ עברי
כ-metadata ולא ככ-key), G2 (אין מסלול-אחסון מקביל — תיקון ה-choke-point היחיד).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 11:30:25 +00:00
c7c402e7ef feat(operations): manual burst control for the halacha drain + permanent supervisor
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
The halacha-extraction backlog needs to be worked off the chair's leftover weekly
Claude quota on demand. This adds a MANUAL, time-boxed "burst" — run the drain
continuously now until a chosen deadline (default the upcoming Saturday 18:00 IL),
managed interactively from /operations — plus the permanent health-supervisor that
enforces it.

Backend (this PR; deploys via Coolify + host pm2):
- db: drain_controls.burst_until (SCHEMA_V37) + set_drain_burst/get_drain_burst/
  get_drain_bursts. Single source of truth shared by the container-side /operations
  API and the host-side supervisor.
- web: POST /api/operations/drains/{name}/burst (on→until|next-Sat-18:00, off→NULL),
  and burst_until surfaced per-service in the /operations snapshot.
- scripts/halacha_drain_supervisor.py + legal-halacha-supervisor.config.cjs: pm2 cron
  (*/15, zero Claude quota) — re-triggers idle drain, restarts a HUNG run (liveness =
  per-chunk checkpoints, NOT log mtime), backs off on 429 until the parsed reset
  (fresh-gated), verifies crash-safe staging. Reads burst_until from the DB; burst
  auto-expires at the deadline (never bleeds into a fresh week).

UI (separate follow-up PR, after Claude Design approval): the /operations toggle +
date-picker that calls the burst endpoint.

Invariants: G1 (normalize at source — burst lives once in the DB, read by both
surfaces), G2 (no parallel control path — CAPTURE field on the existing
drain_controls + orchestrates the existing drain, not a new one), G12 (no Paperclip
touch), §6 (no silent error-swallow — burst-clear failure is surfaced as a note).
2026-06-12 11:11:13 +00:00
959cb093b4 feat(learning): אינדיקציית-תיק למצב למידת-קול + חילוץ-הלכות אחרי החלטה סופית
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
אחרי העלאת החלטה סופית והרצת שני הפייפליינים האוטומטיים (למידת-קול,
חילוץ/אימות-הלכות), התיק לא הציג אם כל תהליך בוצע/הצליח/למה-נכשל. במיוחד
תקלת chair_name ריק (2026-06-12) שמפילה בשקט את העתק-ה-case_law → חילוץ-הלכות
לא מתחיל בכלל, בלי שזה גלוי. כעת מוצגות שתי אינדיקציות ליד כפתורי-ההרצה.

Backend (גזירה ממקור-יחיד, ללא מסלול-מעקב מקביל):
- SCHEMA_V36: draft_final_pairs.learning_run (JSONB) — שדה-תיעוד על פנקס-ההתאמה
  (INV-LRN4), חותם את תוצאת-הריצה של פייפליין-הלמידה (succeeded/failed+סיבה+at).
- set_learning_run_outcome() — חיתום הצלחה/כישלון על ה-pair האחרון.
- case_learning_status() — גזירה read-only מ-draft_final_pairs/style_corpus/
  decision_lessons/case_law/halachot: בוצע? הצליח? למה-לא? כמה הלכות חולצו.
- final_learning_pipeline.py — חותם outcome בהצלחה וב-except (surfaced, לא בלוע).
- חשיפה: case_get מוסיף learning_status (→MCP + /api/cases/{case}/details) +
  endpoint ייעודי GET /api/cases/{case}/learning-status (אותה פונקציה — בלי כפילות).

UI (אושר דרך שער-העיצוב Claude Design — כרטיס 21-final-learning-status):
- useCaseLearningStatus (api/learning.ts) — hook + polling עדין בזמן in-flight.
- LearningStatusBadges — 2 שורות (למידת-קול / חילוץ-הלכות) עם badge + תת-שורה
  (מס' לקחים · רישום-קורפוס / מס' הלכות + פירוק אושרו/ממתינות/נדחו / סיבת-כישלון).
- שילוב ב-drafts-panel תחת "החלטה סופית של היו״ר" + אינוולידציה בכפתורי-ההרצה.

אומת מול ה-DB החי: הצליח+5 הלכות (8174-12-24) · נכנס-אך-pending (1200-12-25) ·
לא-נכנס-לקורפוס (8125-09-24) · round-trip חיתום-כישלון. tsc/eslint נקיים.

Invariants: G1 (נרמול-במקור — גזירה, לא טלאי), G2 (אין מסלול מקביל — שדה על
הפנקס הקיים + exposer יחיד), INV-LRN4 (פנקס-ההתאמה), INV-IA1 (מקור-אמת יחיד).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:50:12 +00:00
2962538c09 feat: תיקון-ציטוט בדלי-החילוץ + קישור-לתור מדף-פרט (#133 follow-ups)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
אושר ב-Claude Design (כרטיס 20-halacha-followups).

א׳ תיקון-חילוץ אמיתי ל-quote_unverified:
- `update_halacha` מקבל `supporting_quote`; בעדכונו מריץ `_verify_quote`
  הקיים מול `case_law.full_text` השמור (דטרמיניסטי — בלי OCR/LLM מחדש,
  feedback_no_reocr_retrofit) ומסנכרן `quote_verified` + מוסיף/מסיר את
  הדגל `quote_unverified`. יו"ר שמדביק את הנוסח הנכון מהמקור → הדגל נמחק
  → ההלכה עוזבת את דלי-החילוץ. `HalachaUpdateRequest`+handler מעבירים את
  השדה; `HalachaPatch` + מצב-העריכה ב-HalachaCard כוללים textarea-ציטוט
  (נשלח רק כששונה) + hint.

ב׳ דף-פרט פסיקה — ביטול כפילות-המשטח:
- הלכה pending ב-`ExtractedHalachotSection` מציגה קישור "עבור לתור הלכות"
  במקום כפתורי אשר/דחה כפולים (שער-אישור יחיד, INV-IA/G10).
- `/precedents` Tabs הפך נשלט וקורא `?tab=review` (post-mount, בלי
  hydration-mismatch) כדי שהקישור ינחת על טאב-התור.

display-only ל-G10 (האימות מסנכרן מטא-איכות, לא review_status). ולידציה:
py_compile + tsc + eslint נקיים.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 09:03:29 +00:00
b49cde7c24 feat(missing-precedents): חיפוש-טקסט לפי מראה-מקום בדף פסיקה-חסרה
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
הבעיה: בדף /missing-precedents לא ניתן היה לאתר פסיקה חסרה לפי מספר ההחלטה
החסרה עצמה (למשל 85074). השדה היחיד לחיפוש-תיק עשה get_case_by_number על
מספר ה-ערר שבו צוטטה הפסיקה — ולכן הקלדת מספר-הפסיקה החזירה רשימה ריקה,
למרות שהרשומה קיימת (ערר (ת"א 85074-04-25) ... status=open).

התיקון (הרחבת השדה הקיים, ללא עמוד/שדה חדש — בהנחיית חיים):
- db.list_missing_precedents: פרמטר q חדש — ILIKE על mp.citation +
  mp.case_name + cited-in c.case_number (אינדקס-פרמטר יחיד, additive;
  שאר הקוראים לא נוגעים).
- GET /api/missing-precedents: פרמטר q; case_id/case_number נשארים
  מסננים-מדויקים לקוראים תכנותיים.
- web-ui: התווית "תיק (מספר ערר)" → "מספר תיק", placeholder
  "85074 או 1017-03-26"; השדה שולח q (חיפוש חופשי) במקום case_number.
  Debounce 350ms נשמר.

api:types לא חודש: ה-hook בונה את ה-querystring ידנית וה-response לא
השתנה; חידוש מול prod (שעוד לא נפרס) רק היה מושך drift לא-קשור.

בדיקות: tsc --noEmit נקי, eslint נקי על הקבצים שהשתנו, py_compile נקי.

Invariants: G2 (הרחבת היכולת הקיימת, לא מסלול-חיפוש מקביל), INV-IA1
(שער/דף יחיד לפסיקה-חסרה — בלי עמוד חדש), §6 (ללא בליעת-שגיאות).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 07:58:29 +00:00
4f7c3733e2 fix(ingest): read staged file via storage.ensure_local — s3-only upload 500 (INV-STG1)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
תיקון: העלאת פסיקה/החלטת-ועדה (precedent-library + internal-decisions) נכשלה
תחת backend s3-only עם "Package not found at '/data/...docx'" / "Converted file
not found". השורש: ‎`ingest._stage_file` כותב את הקובץ דרך ‎`storage.put_file`
ומחזיר נתיב‎-DATA_DIR, אבל תחת s3-only ה‎-blob נכתב רק ל‎-MinIO ואין עותק בדיסק —
ואז הצינור קרא את הנתיב ישירות מהדיסק (extract_text) → קובץ לא קיים. מסלול
תיקי‎-המקרה לא נפגע כי הוא שומר עותק‎-דיסק + mirror_file; רק מסלול ‎_stage_file
המשותף קרא את ה‎-key כאילו הוא על הדיסק.

התיקון (נרמול‎-במקור, G1; קריאה דרך שכבת‎-האחסון, INV-STG1):
- ‎`_stage_file` מחזיר עכשיו את ה‎-KEY (נתיב יחסי‎-DATA_DIR), לא Path.
- ‎`ingest_document` ו‎-‎`digest_library` מאתרים נתיב‎-קריאה מקומי דרך
  ‎`storage.ensure_local` (עותק‎-דיסק תחת filesystem/dual; הורדה ל‎-temp תחת
  s3-only) ומנקים את ה‎-temp ב‎-finally — בלי דליפה ל‎-/tmp.
- מולטימודל (PDF) קורא את אותו נתיב מקומי מאומת.

בדיקות: test_unified_ingest::test_ingest_reads_via_ensure_local_when_no_disk_copy
מדמה backend ללא עותק‎-דיסק ומוודא שהצינור משלים (נכשל מול הקוד הישן). 55 עוברות.

Invariants: מקיים INV-STG1 (קריאה/כתיבה רק דרך שכבת‎-האחסון), G1 (נרמול‎-במקור,
לא תיקון‎-בקריאה), G2 (אין מסלול מקביל — תיקון הצינור הקנוני).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 07:32:04 +00:00
ca1a0ddaac Merge pull request 'fix(learning): chair_name במקור — סופי-ועדה תמיד נכנס לקורפוס-הפסיקה (#134)' (#226) from worktree-chair-name-rootfix into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m30s
G12 Leak-Guard / leak-guard (push) Successful in 6s
2026-06-12 07:26:32 +00:00
242e6cfd11 fix(learning): chair_name במקור — סופי-ועדה תמיד נכנס לקורפוס-הפסיקה (TaskMaster #134)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
הבאג: שלב-הלמידה (ingest_final_version → ingest_internal_decision) מוסיף כל
סופי כתקדים ציטוטי ב-case_law (source_kind=internal_committee), אך נכשל
בשקט (non-fatal warning) כש-cases.chair_name ריק — בגלל constraint
case_law_internal_chair_check. כך סופיים של 1194/1200/8070 לא נכנסו
לקורפוס-הפסיקה. שורש: (1) chair_name לא נקבע בפתיחת תיק; (2) מסלול-ה-MCP
העביר chair גולמי בעוד מסלול-ה-UI (web/) כבר פתר אותו דטרמיניסטית —
**מסלולים מקבילים מתפצלים (הפרת INV-G2)**; (3) הכשל נבלע (נגד §6).

תיקון-שורש (3 שכבות):
1. **SoT יחיד (INV-G2):** `config.committee_chair_for_case` — המקום היחיד
   שגם web/app.py וגם tools/workflow.py + db.create_case גוזרים ממנו chair
   (לפי תחילית מספר-התיק; override ל-env). web/ אחוד אליו (הוסרה הכפילות).
2. **נרמול-במקור (INV-G1):** `db.create_case` קובע chair_name תמיד לא-ריק;
   `cases.case_create` חושף param. `ingest_final_version` גוזר chair מה-SoT
   במקום הערך הגולמי → ה-constraint לא נופל.
3. **נראות (§6/feedback_silent_swallow):** כשל-העתק מוחזר ב-result
   (`internal_corpus_error`) ו-`final_learning_pipeline` מדפיס אזהרה — לא
   נבלע. backfill ל-11 תיקים עם chair ריק. `audit_corpus_integrity`:
   נוספו CHECK_D (תיקים מוכרעים ללא chair) + CHECK_E (סופי-final חסר
   מקורפוס-הפסיקה) — שניהם 0 כעת.

invariants: מקיים INV-G1 (נרמול בכתיבה), INV-G2 (מסלול-יחיד, אוחד web↔MCP),
§6 (אין בליעה שקטה). בדיקות: py_compile + 14 pytest (chair_seed_gate,
audit_provenance) + integration של create_case (default+override) + הרצת
ה-audit החי (A–E=0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 07:25:54 +00:00
d246fb85fc feat(learning): FU-5 — מדידת לולאת-הלמידה מול הכרעות-היו"ר (#133)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
מרחיב את halacha_panel_calibrate.py כדי למדוד את הלולאה לאורך-זמן ולשמור
על בריאותה — סוגר את 5 ה-FU של #133.

- --source captured (חדש, אפס-עלות): מצליב סבבי-פאנל שמורים (FU-1) מול
  הכרעות-היו"ר (FU-2) דרך db.panel_rounds_vs_chair, ומדווח split-rate +
  auto-precision + false-keep/false-drop **לכל סבב (per round-day)** מול
  ה-gold-set הגדל. כך רואים את הלולאה עובדת: ככל שהרובריקה משתפרת
  (FU-4 → אימוץ-יו"ר) — precision נשמר ו-split יורד. בלי re-vote, בלי LLM.
- summarize_calibration() + bucket_by_round() — עוזרים טהורים (offline-
  testable). משתפים את analyze_pairs של FU-4 → "מה נכשל" מחושב במקום אחד
  (בלי drift, G2).
- anon-stability: שתי המדידות מדווחות את שיעור-יציבות מבחן-האנונימיזציה
  (#81.7) כמטריקת-בריאות נגד echo-chamber — נפילה = שינון במקום היגיון.
- --source live (קיים): נוסף עמוד split-rate מפורש + anon-stability.
- tests/test_panel_calibrate_captured.py — 5 בדיקות offline. SCRIPTS.md
  עודכן. smoke read-only עבר (0 זוגות → nothing-to-measure).

Invariants: read-only מדידה · INV-G10 (האמת=הכרעת-יו"ר) · anti-echo-
chamber (anon-stability) · G2 (analyze_pairs מקור-יחיד). רגרסיה 30 עברו.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 07:19:48 +00:00
4cad17df3a feat(learning): FU-4 — זיקוק-רובריקה propose-only מהכרעות-היו"ר (#133)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
job תקופתי שסוגר את לולאת-הלמידה: מצליב את סבבי-הפאנל (FU-1, הצבעות+
נימוקים) מול הכרעות-היו"ר (FU-2 seeds), מזהה כשלים שיטתיים, ומציע
KEEP_SYSTEM v2 + exemplars מופשטים — כדוח-diff לעיון-היו"ר. לעולם לא
auto-applied.

- db.panel_rounds_vs_chair() — read-only LATERAL join: לכל הלכה עם seed
  chair-live (FU-2, אמת אנושית) + סבב-פאנל אחרון (FU-1) → הצבעות+נימוקי-
  3-השופטים מול keep/drop של היו"ר. הסיגנל היחיד = הכרעת-יו"ר, לא
  הצבעות-הפאנל (anti-echo-chamber, INV-LRN1).
- scripts/halacha_rubric_distill.py:
  • analyze_pairs() — ליבה דטרמיניסטית טהורה (offline-testable): false-keep
    (פאנל שמר, יו"ר דחה), false-drop, פיצולים-שהוכרעו, שיעור-מחלוקת-עם-
    היו"ר לכל שופט; בוחר ראיות-מחלוקת מכוסות.
  • הצעת-LLM מקומית (claude_session, tools="", אפס עלות): מזהה דפוסי-כשל
    ומציע נוסח-רובריקה v2 + exemplars מופשטים (INV-LRN5 — בלי מהות-תיק).
  • כותב data/learning/rubric-proposal-<ts>.md עם diff(KEEP_SYSTEM→v2);
    אף שורת-קוד לא משתנה. אימוץ = עריכה ידנית דרך PR (INV-LRN1).
  • <12 זוגות → "אין מספיק נתונים" (מצב נוכחי: seeds עדיין מצטברים).
  • --no-llm (סטטיסטיקה בלבד) / --limit N.
- tests/test_rubric_distill.py — 8 בדיקות offline על analyze_pairs.
- SCRIPTS.md עודכן. smoke read-only עבר (0 זוגות → insufficient-data).

תואם הדפוס הקיים (style_lesson_panel/halacha_panel_audit): פאנל מציע,
הטמעה נשארת שער-יו"ר ידני. Invariants: INV-LRN1 (propose-only) ·
INV-LRN5 (טוהר-רובריקה) · INV-G10 · anti-echo-chamber. בלי שער/UI חדש.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 06:59:34 +00:00
d98ef14f41 feat(learning): FU-3 — uncertainty-sampling של תור-האישור לפי מחלוקת-הפאנל (#133)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
תור-אישור-ההלכות הקיים (order_by_priority, #84.3) מקדם עכשיו את ההלכות
שהפאנל התלבט עליהן: split קודם, אחר-כך incomplete — התוויות בעלות-הערך
הגבוה ביותר ללולאת-הלמידה (הכרעת-היו"ר מפרקת אי-ודאות אמיתית ומזינה את
זיקוק-ה-rubric ב-FU-4). uncertainty-sampling על סיגנל-המחלוקת האמיתי של
הפאנל, לא רק confidence-החילוץ.

- list_halachot: LEFT JOIN לאחרון-הסבבים (DISTINCT ON latest round_ts מ-
  halacha_panel_rounds) + מפתח-מיון ראשי CASE verdict split→0/incomplete→1/
  else→2, לפני מפתחות #84.3 (corroboration→confidence→age). סבבים פה-אחד
  ופריטים-ללא-סבב נשארים בזנב עם הסדר הקיים.
- panel_verdict נחשף בכל שורה (UI יכול לתייג "פיצול" + ביקורת-סדר).
- שימוש חוזר בדגל order_by_priority הקיים ובטאב הקיים — בלי מסלול/דגל
  מקביל (G2). ה-UI כבר מבקש order_by_priority=true → אפס שינוי-UI, אין
  צורך בשער-עיצוב.
- test_halacha_priority_panel_order.py: 3 בדיקות offline (SQL-capture) —
  מפתח-מחלוקת ראשי בעדיפות, FIFO ללא דליפת-CASE, panel_verdict נבחר.

Invariants: INV-G10 (capture-only, לא משנה review_status) · G1/G2 ·
INV-IA (אותו שער/טאב). רגרסיה: 76 בדיקות עברו.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 06:47:58 +00:00
d292c06ecd Merge pull request 'feat(learning): FU-2 UI — התלבטות-הפאנל במסך-אישור היו"ר (#133)' (#220) from worktree-halacha-deliberation-ui into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m46s
G12 Leak-Guard / leak-guard (push) Successful in 9s
2026-06-12 06:19:50 +00:00
9c6896df90 feat(learning): FU-2 UI — התלבטות-הפאנל במסך-אישור היו"ר (#133)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
מציג את התלבטות 3-השופטים (הצבעה+נימוק לכל לינאז' + ה-verdict)
בתוך כרטיס-האישור הקיים של דפנה ב-/precedents → "ממתין לאישור",
כדי שהכרעתה — תווית-הזהב שהלולאה לומדת ממנה — תהיה מיודעת ב*למה*
הפאנל נחלק. אושר ב-Claude Design (כרטיס 18-halacha-deliberation).

Backend (opt-in, ברירת-מחדל off — קוראים קיימים לא מושפעים):
- db.list_halachot(include_panel_round=True) → _annotate_panel_rounds
  מצרף את הסבב האחרון מ-halacha_panel_rounds (DISTINCT ON, latest).
- GET /api/halachot?include_panel_round=true.

Frontend:
- Halacha.panel_round (טיפוס ידני; ה-endpoint מחזיר dict).
- תור-הסקירה (useHalachotPending) מבקש include_panel_round בשני
  הדליים (clean=keep, needsFix=nli/entailed).
- רכיב PanelDeliberation: טבלת 3-שופטים (✓נתמך/✗הכלל-חורג + נימוק),
  תג-ורדיקט "פיצול 2:1", ושורת "שורש המחלוקת" (קפדני↔תמצית) רק
  בפיצול-entailment. מוזרק אחרי רשת הכלל/ציטוט.

שער יחיד — אין עמוד/שער חדש (INV-IA/G10); display-only, לא נוגע
ב-review_status. ולידציה: py_compile + tsc --noEmit + eslint נקיים;
בדיקה פונקציונלית: panel_round מצורף ל-6 שיש להן סבב, 1994 בלי.

חלק מ-#133 (FU-2). דורש deploy + (אופ') npm run api:types אחרי.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 06:19:15 +00:00
e8bcb9c1ea fix(cases): מספור 5-ספרתי לבל"מ — סיווג, ולידציה, וחיפוש פסיקה-חסרה
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
נוהל-יו"ר (2026-06-11): מבנה מספר-תיק = <סידורי>-<חודש>-<שנה>, ואורך הסידורי
מקודד את סוג-ההליך — 4 ספרות = ערר, 5 ספרות = בל"מ. הספרה הראשונה ממשיכה
לקבוע תחום בשני האורכים (1→רישוי, 8→היטל, 9→פיצויים). הכלל חד-כיווני:
5-ספרתי הוא תמיד בל"מ; 4-ספרתי אינו מחייב ערר (בל"מ-מורשת מזוהה מהנושא).

הבאג שדיווח עליו היו"ר: חיפוש פסיקה-חסרה לפי מספר-תיק החזיר 404 על כל ערך
שאינו תיק קיים — שבר את הטבלה תוך כדי הקלדה ועל מספרי 5-ספרות.

תיקונים:
- web/app.py: GET /api/missing-precedents — מסנן case_number שלא תאם תיק מחזיר
  רשימה ריקה (200), לא 404. סמנטיקה תקינה ל-collection-filter.
- missing-precedents/page.tsx: debounce (350ms) על שדות-הסינון — קוורי אחד
  אחרי שמפסיקים להקליד, לא אחד לכל הקשה.
- practice_area.py: regex סידורי \d{4}→\d{4,5}; case_serial_digits() +
  is_blam_by_number() (5⇒בל"מ); derive_subtype_with_blam ו-derive_proceeding_type
  מזהים בל"מ גם מ-5-ספרות (בנוסף לנושא). callers: cases.py, internal_decisions.py.
- proofreader.py: דפוסי חילוץ-שם-קובץ \d{3,4}→\d{3,5}.
- web-ui: practice-area.ts (מראָה ל-backend), schemas/case.ts (regex
  serial-month-year, 4-or-5 ספרות, superRefine 5⇒בל"מ), placeholder בוויזרד.
- תיעוד: docs/spec/X1-identifiers.md §1א + legal-ai/CLAUDE.md.

Invariants: מקיים G1 (נרמול-במקור — ספרה ראשונה כמקור-אמת יחיד לתחום),
G2 (מסלול-סיווג יחיד, אין כפילות), INV-DM/X1 (מפתח קנוני + proceeding_type).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 06:16:42 +00:00