When the halacha drain hit a 429, the supervisor recorded the reset time the
error reported (e.g. "resets 6:50pm UTC") and then HELD until that timestamp,
re-reading it from its own state every tick without ever checking whether quota
had actually returned. claude.ai usually frees up quota earlier than the message
claims, so the drain sat idle for hours after it could have resumed — and only a
manual kick (clear cooldown + trigger) got it going again.
Now, on any tick where we'd otherwise hold on a cooldown, run a cheap live probe
(`quota_available()` → a tiny `claude -p` call, cost ~0) and resume the instant
it succeeds — at most one probe per 15-min tick, only while we believe we're
limited. Conservative on failure (non-zero exit / timeout / limit message →
stay held), so a flaky probe never resumes the drain into a real 429. Adds a
claude_bin() resolver so the probe works under pm2 cron where PATH is bare.
Invariants: G1 (resume decision driven by actual quota state, not a guessed
timestamp); no new control path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /operations "disabled" toggle only wrote drain_controls.disabled, which the
drain checks at STARTUP — so a drain already mid-run kept going until the queue
emptied or the night window closed. Disabling did not stop a running drain.
Three layers, immediate + backstops:
- web/app.py operations_drain_toggle: on disable, also stop the running process
immediately via the host pm2 bridge (_ops_pm2_control). Best-effort — a bridge
failure doesn't fail the toggle.
- halacha_drain_supervisor.py: each tick now reads the disabled flag (added to
db_snapshot) and, when set, stops the drain and never re-triggers it —
regardless of burst/window. Backstop if the UI path failed (≤ one tick).
- drain_halacha_queue.py: re-check is_drain_disabled at the top of every round,
so a drain disabled mid-run halts at the next round boundary. Per-chunk
checkpoints mean the in-flight case loses nothing.
SCRIPTS.md updated for both drain and supervisor.
Invariants: G1 (fix at source — the disable control honoured along every path,
not just at startup); G2 (no parallel control path — same drain_controls flag).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
The host pm2 supervisor imports legal_mcp.services.db from the host repo checkout,
which can lag main by many commits. Depending on the just-added db.set_drain_burst/
get_drain_burst would require the host checkout to be current. Use raw SQL via the
stable db.get_pool() instead — the supervisor now depends only on get_pool + the
drain_controls.burst_until column (the shared contract with the /operations API).
The container-side API keeps using the typed helpers (it ships the code in-image).
Invariants: G1/G2 unchanged (same single DB column, no parallel path).
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).
אחרי העלאת החלטה סופית והרצת שני הפייפליינים האוטומטיים (למידת-קול,
חילוץ/אימות-הלכות), התיק לא הציג אם כל תהליך בוצע/הצליח/למה-נכשל. במיוחד
תקלת 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>
FU-1 (#214) הוסיף ל-halacha_panel_approve.py את הדגל --no-capture
ואת השימוש `if not args.no_capture` בשלב-הלכידה. אבל
final_halacha_pipeline.py קורא ל-hpa.main() עם Namespace שנבנה ביד
(limit/concurrency/apply בלבד) — בלי no_capture. לכן הרצת הצינור
("הרץ הלכות") קרסה ב-AttributeError בדיוק בשלב שמירת-הסבבים, אחרי
שה-apply כבר רץ → 0 סבבים נלכדו לתיק.
תוקן: הוספת `no_capture=False` ל-Namespace. אומת מקצה-לקצה על
8174-12-24 → "captured 49 panel rounds, errors=0".
audit: רק 2 מקומות בונים Namespace ביד לקריאת main() של סקריפט אחר —
זה (תוקן), ו-final_learning_pipeline.py→style_lesson_panel (נבדק, כל
ה-args מסופקים, תקין). אין באגים נוספים מהמחלקה הזו.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
One-time host migration (executed 2026-06-12): adds the missing 2-digit month
to 11 case numbers (and corrects 1046-26 → 1024-02-26, a wrong serial).
All legal-ai FKs are on cases.id (UUID) → untouched. The script atomically
migrates, per case, everything that embeds the number as TEXT:
· cases.case_number + every column containing 'cases/{old}/' (file_path AND
image_thumbnail_path — the latter is a DATA_DIR-relative storage key with
no '/data' prefix, hence the slash-less needle)
· disk dir + case.json
· MinIO keys across 3 buckets (legal-immutable = WORM, copy-only)
· Gitea repo rename + local .git remote + description
· Paperclip project name
For the 4 archived cases whose final was ingested, the canonical number is
propagated to the precedent + style corpora identifier fields (case_law,
style_corpus, style_exemplars, citations) per chair decision — document
content / full_text / OCR text is left as the historical record.
Verified: 0 stale identifier/path refs across all 11; documents, thumbnails,
drafts, Gitea, Paperclip all resolve under the new numbers. Per-case backups
in data/audit/renumber-*.json.
Invariants: G1 (normalise at source — single rename op, not read-time patch),
G2 (no parallel path — reuses the app's DB pool + storage semantics),
G12 (Paperclip touched only via its declared surface).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
לולאת ה-active-learning זקוקה לסיגנל ללמוד ממנו, אבל הפאנל
(halacha_panel_approve.py) זרק עד כה את הצבעות-3-השופטים ואת
ההנמקות — שרד רק review_status הסופי על halachot. בלי
ההצבעות+הנימוקים אין דרך לזקק rubric משופר.
FU-1:
- טבלה חדשה halacha_panel_rounds (SCHEMA_V35) — שורה לכל
(הלכה, סבב): הצבעה+נימוק לכל לינאז' (claude/deepseek/gemini),
ה-verdict, ומה הריצה עשתה (applied_action), apply_mode.
במתכונת עמודות-הפאנל של halacha_goldset.
- db.insert_panel_round() — helper כתיבה (capture-only).
- halacha_panel_approve.py: שומר את התשובות הגולמיות (במקום
לזרוק את הנימוק), מוסיף reason ל-NLI_SYSTEM, וכותב סבב לכל
פריט בשני המצבים (dry-run ו---apply). --no-capture לדילוג.
capture-only: לעולם לא נוגע ב-halachot — שער-היו"ר ב-/precedents
נשאר מקור-האמת היחיד (INV-G10). ה-seed ללמידה נוצר בהצלבה מול
הכרעת-היו"ר המאוחרת על אותה הלכה (FU-2).
Invariants: מקיים INV-G10 (capture-only, שער-יו"ר יחיד),
INV-LRN1/3 (לכידה-מבנית; propose-only — אין auto-commit),
G1 (לכידה-במקור), G2 (יכולת חדשה, לא מסלול-מקביל),
G12 (לא נוגע ב-Paperclip port). חלק מ-#133.
smoke (dry-run --limit 8): 6 nli captured, errors=0, נימוקים
מלאים מ-3 השופטים.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
מונה "ממתין (בקלוג)" ב-/operations הציג 140 פריטים תקועים שהדריינר (Gemini, כל
15 דק') דיווח עליהם total_pending=0 — אי-התאמה בין שתי הגדרות-תור:
ה-UI סופר status='pending' (ברירת-מחדל של העמודה), בעוד הדריינר סורק רק
metadata_extraction_requested_at IS NOT NULL. שורות שקיבלו מטא במסלול אחר
(internal דטרמיניסטי, cited_only חסר-טקסט) נשארו על ברירת-המחדל 'pending' לנצח.
פילוח ה-140: 82 internal_committee (מטא דטרמיניסטי, מחוץ לצנרת-Gemini) ·
31 cited_only (אין טקסט לחלץ) · 27 external_upload (כבר מלאים).
תיקון-במקור (G1 — נרמול במקור, לא תיקון-בקריאה):
- db.create_internal_committee_decision: INSERT + ON CONFLICT קובעים
metadata_extraction_status='completed' ישירות → שורות פנימיות לא נכנסות
שוב למצב-הרפאים.
- scripts/reconcile_metadata_status.py: נרמול חד-פעמי/re-runnable של שורות
קיימות (internal/external מלא→completed · external חסר→requeue · cited_only→skipped).
הורץ: 82+27→completed, 31→skipped, pending=0.
- web-ui /operations: התווית "ממתין (בקלוג)" → "ממתין" (הסרת המילה הלועזית)
+ tooltip מדויק; הערת operations.ts מעודכנת.
Invariants: מקיים G1 (normalize-at-source) ו-INV-IA (מונה-אמת/מקור-אמת-יחיד).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
מגיש את scripts/SCRIPTS.md כדף ב-/scripts: שם · סוג · תפקיד · תזמון
לכל סקריפט בתיקיית scripts/. מקור-האמת היחיד נשאר SCRIPTS.md (G2 — אין
מסלול-תוכן מקביל); עריכה דרך git, לא מה-UI.
- web/app.py: GET /api/scripts/catalog קורא את הקובץ בזמן-ריצה (מחקה את
דפוס get_curator_prompt; HTTPException על כשל — אין בליעה שקטה §6)
- Dockerfile: COPY scripts/SCRIPTS.md (לא הועתק לקונטיינר עד כה)
- web-ui: דף /scripts (AppShell + רכיב Markdown הקיים) + מודול api + קישור ניווט
- SCRIPTS.md: תיעוד ingest_bulletins.py — היה הקובץ היחיד מ-73 שלא תועד
Invariants: G2 (מקור-אמת יחיד), G12 (אין מגע-Paperclip), X6 (UI↔API).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
מצב --untracked לסקריפט ההגירה: סורק את ה-filesystem לקטגוריות שה-4 endpoints מגישים
אך אינן רשומות בשום עמודת-DB (research/*, proofread/*, drafts/*, exports/*, training/
proofread/*) → מעלה ל-legal-documents עם אותו key יחסי-DATA_DIR. זהו תנאי-הסף שהפאנל
התלת-מודלי זיהה: בלי הקבצים האלה ב-MinIO, cutover ל-s3-only היה מחזיר 404 על הגשתם.
dry-run אומת: 144 קבצים / 83.9MB, 0 חסרים, 0 outside. הפיך (העתקה אדיטיבית, דיסק שלם).
refactor קטן: הלולאה הראשית עובדת על work-list אחיד (DB-tracked או filesystem-scan).
invariants: G2 (אותו key/bucket scheme) · INV-STG1/3 · INV-G10 (dry-run/הפיך, אפס שינוי
בייצור — רק העלאה לדליות; cutover עדיין נעול-אדם).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ריצת-הפאנל החיה חשפה Fleiss κ=-0.07 למרות 97.5% הסכמה-גסה (28/40 פה-אחד, 11/40 רוב).
זה אינו חוסר-אמינות אלא **פרדוקס-הקאפא**: ה-marginal של is_holding מוטה קיצונית
(≈הכול True, כמו 93/100 ה-keep בתוויות-האנוש), וכש-Pe→1 גם κ→0 (Feinstein & Cicchetti
1990, "high agreement, low kappa").
- gwet_ac1(): מדד הסכמה עמיד-שכיחות (Gwet 2008) — אותו Pa כמו Fleiss, אומדן-מקריות שונה
(2·p·(1-p)). הופך לכותרת; Fleiss κ עדיין מודווח לשקיפות + raw 3/3.
- consensus-vs-HUMAN: כשקיים תיוג-יו"ר, הדוח מודד התאמת-הקונצנזוס מולו (תוקף חיצוני).
אימות בפועל על 100 תוויות-היו"ר: 29/29 = 100% התאמה.
invariants: ללא שינוי בהתנהגות-הכתיבה; מטריקה בלבד. tests: 21 (3 חדשות, כולל מקרה-פרדוקס מפורש).
מקור: Gwet 2008 (AC1) · Feinstein & Cicchetti 1990.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
מפריד בין ריקון-באקלוג המוני לבין חילוץ per-upload, ומסיר את ה"פקק" שגרם
timeout/process_lost ב-heartbeat של ה-CEO.
הבעיה (אבחנה 2026-06-11): לחיצת "חלץ הלכות" על תיק בודד יצרה issue (CMP-165)
שהורה ל-CEO להריץ precedent_process_pending(halacha) — בולען סינכרוני שמרוקן את
כל התור ההיסטורי (147 ממתינים, שעות) בתוך heartbeat שחסום לשעה. תוצאה: timeout
כל שעה → process_lost בפירוק קבוצת-התהליכים → retry → סטורם, והתיק הבודד (FIFO
אחרון) לא טופל. לא OOM, לא קוד שבור — אי-התאמה ארכיטקטונית.
התיקון:
1. per-upload (web/paperclip_client.py, wake_for_precedent_extraction): גוף ה-issue
+ תיאור-הפרויקט מורים כעת להריץ precedent_extract_metadata +
precedent_extract_halachot ל-case_law_id של ה-issue **בלבד** — עם אזהרה
מפורשת לא להריץ process_pending. reextract_halachot כבר מנקה requested_at
ומסמן completed → התיק לא יחזור לתור הלילי.
2. הוראות ה-CEO (.claude/agents/legal-ceo.md): אותו שינוי — חילוץ תיק-בודד, לא
ריקון-תור. (צריך sync_agents_across_companies.py --apply אחרי מיזוג.)
3. ריקון-באקלוג (scripts/drain_halacha_queue.py): שער חלון-לילה 23:00–05:00 שעון
ישראל (zoneinfo, DST-safe — המכונה UTC). מחוץ לחלון ===SKIP===; נעצר ===STOP===
כשהחלון נסגר, השאר ממשיך בלילה הבא (FIFO + per-chunk checkpoint). env:
HALACHA_DRAIN_WINDOW_START/_END/_TZ.
4. cron (scripts/legal-halacha-drain.config.cjs): UTC band 20:00–03:00 שמכסה את
חלון-ישראל בשני מצבי-DST; הסקריפט גוזם לחלון המדויק. ירייה שעתית מחדשת
one-shot שמת (advisory-lock → חפיפה בטוחה).
רשת-ביטחון: request_halacha_extraction עדיין מסמן requested_at, כך שאם wakeup
ל-CEO נכשל — הדריינר הלילי יתפוס את התיק (בלילה, חסום), אך שום נתיב יומי לא
מרוקן את כל התור.
Invariants: מקיים G12/INV-PORT1 (paperclip_client = shell; leak_guard עובר).
נוגע X16 (durability — מתקציב-זמן heartbeat ל-job ייעודי).
בדיקות: py_compile ✓ · window-logic + zoneinfo ✓ (17:00 IDT→False) · leak_guard ✓.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
מחיל את scripts/_pipeline_runtime.py (מ-P0) על final_learning_pipeline: 3 הצעדים
([1]ingest/Opus-distillation [2]enroll-style-corpus [3]style-panel) רצים דרך אותו
runtime עמידות — מימוש אחד לשני הפייפליינים (G2), לא מימוש מקביל.
קריסה/OOM בפאנל-הסגנון [3] ממשיכה מ-[3] במקום לשלם שוב על דיסטילציית-ה-Opus [1]
(היקרה). thread יציב לכל תיק (learning:{case}); dry-run = preview נפרד. CLI זהה +
--fresh. שגיאת ingest קריטית → raise → halt + clean non-zero exit (resume מנסה שוב).
degradation חיננית כמו ב-P0 (ללא langgraph → ליניארי).
אימות: py_compile OK; מיובא נקי ב-venv המשותף (langgraph נעדר, lazy import). מנגנון
ה-runtime עצמו מכוסה ב-test_pipeline_runtime.py (P0) — אותו runtime.
Invariants: INV-DUR1 (עמידות), G2 (runtime יחיד), G3 (idempotency).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
style_lesson_panel.py: before writing 2/2-keep lessons, skip any whose normalized
lesson_text already exists on the corpus (any source), and collapse duplicates within
a run. Makes the run-learning button safe to click repeatedly (the curator may re-run
the pipeline) — it converges instead of piling up duplicate decision_lessons.
Verified on בל"מ 8126-03-25: re-running --apply with 7 existing lessons wrote 0
("1 כפילויות דולגו"), count stayed 7.
Invariants: INV-LRN1/G10 unchanged (proposals only, manual fold).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
הכפתורים "הרץ למידת-קול"/"הרץ אימות-הלכות" מעירים את הרמס, ובמקום שהסוכן
(DeepSeek) ירכיב כמה קריאות-כלי (שביר), הוא מריץ עכשיו פקודה דטרמיניסטית אחת.
חדש:
- scripts/final_learning_pipeline.py — (1) ingest_final_version עם נתיב-הסופי
(מדלג אם הזוג כבר analyzed; --force לחידוש), (2) רישום לקורפוס-הסגנון
(idempotent — סוגר את הפער שפאנל-הסגנון דרש corpus_id), (3) style_lesson_panel
--apply. --dry-run להרצה בטוחה.
- scripts/final_halacha_pipeline.py — extract_internal_citations →
corroboration.build_all → halacha_panel_approve --apply. --dry-run / --limit.
briefs הרמס (web/paperclip_client._curator_task_brief) פושטו לפקודה-אחת לכל
task — חסין מול הרצת-סוכן. תוקנו שני הפערים שזוהו: ingest דרש file_path,
ופאנל-הסגנון דרש style_corpus.
נלווה: תיקון help מיושן של halacha_panel_approve (--apply מחווט). SCRIPTS.md.
אומת: שני ה-pipelines רצו dry-run על בל"מ 8126-03-25 (skip-ingest, קורפוס,
פאנלים) בהצלחה. Invariants: INV-LRN1/LRN5/G10 (הפיך, שער-יו"ר ידני נשמר),
INV-DM7. G2 — תזמור של יכולות קיימות, לא מסלול-מקביל.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
מוסיף מסלול ייעודי לקליטת ההחלטה החתומה של היו"ר, ומפעיל אותו דרך שני
שלבים אוטומטיים מדורגים עם פאנלי-סוכנים (אוטו-אישור + אסקלציה ליו"ר).
Backend (web/):
- POST /api/cases/{case}/final/upload — קליטת final חיצוני: שמירה קנונית
(סופי-{case}.docx + עותק קורפוס-סגנון תחת case_number מלא כדי שבל"מ לא
יתנגש עם ערר באותו מספר), פתיחת draft_final_pairs (final_received). לא נוגע
ב-active_draft ולא מריץ retrofit (נבדל מ-exports/upload ו-mark-final → לא G2).
- POST .../final/run-learning + .../final/run-halacha — שלבים מדורגים שמעירים
worker מקומי (claude/DeepSeek/Gemini מקומיים בלבד) דרך הרחבת
wake_curator_for_final עם param task=learning|halacha.
פאנל-סגנון חדש (scripts/style_lesson_panel.py): שני שופטים (DeepSeek+Gemini)
על-גבי דיסטילציית-ה-Opus; הסכמה 2/2-keep → decision_lesson
(source=panel:deepseek+gemini); substance מדולג (INV-LRN5); הפיך + גיבוי CSV.
פאנל-הלכות: docstring/SCRIPTS.md עודכנו (--apply מחווט).
Frontend (web-ui/): כפתור "העלאת החלטה סופית של היו"ר" + שני כפתורים מדורגים
"הרץ למידת-קול"/"הרץ אימות-הלכות" ב-drafts-panel; כל התוויות בעברית
(badge מקור-לקח: "פאנל: דיפסיק+גמיני", "הרמס (סקירה)"...).
Spec: docs/spec/07-learning.md §0.6. Invariants: INV-LRN1/LRN4/LRN5, G10
(שער-יו"ר ידני להטמעה ל-SKILL.md/lessons.md — הפאנלים יוצרים הצעות בלבד);
G2 (מסלול-סופי הוא יכולת חסרה, לא מסלול-מקביל).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
הדף הציג את התורים באופן לא-אחיד (by_status גולמי), בלי הבחנה בין "ממתין"
(בקלוג: status=pending) ל"בתור" (התור הפעיל: requested_at IS NOT NULL), בלי
הצגת הפריט שרץ כרגע, ובלי שום שליטה בתהליכים.
מה נוסף:
1. כרטיסי-תור אחידים — בתור / ממתין(בקלוג) / בעיבוד / הושלם / נכשל + "רץ עכשיו"
(citation/case_number של הפריט בעיבוד) לכל drain (אחזור-פסיקה, מטא-דאטה,
הלכות, יומונים). שערי-אנוש (אישור-הלכות, פסיקה-חסרה) נשארים מוני-סטטוס.
2. פאנל ניהול-תהליכים בסגנון "שירותי Windows":
- דמון (court-fetch-service/xvfb/chat/reaper): הפעל-מחדש / עצור / הפעל.
- cron drain: "הרץ עכשיו" (pm2 restart) + מתג הפעל/כבה תזמון.
3. כל תגי-הסטטוס מתורגמים לעברית.
מנגנון:
- הפעל/כבה תזמון = דגל ב-DB (טבלה drain_controls). pm2 cron_restart מחיה תהליך
שעוצר ב-stop, לכן ה"כיבוי" האמין הוא דגל שכל drain בודק ב-startup (no-op מיידי
כשכבוי). הקונטיינר כותב/קורא ישירות מ-DB.
- הרץ-עכשיו + restart/stop/start = proxy ל-pm2 דרך endpoint חדש בגשר-המארח
(court_fetch_service /pm2/control), מאובטח Bearer + whitelist ל-legal-* בלבד.
- יומונים: drain_digests הועבר מ-crontab ל-pm2 (legal-digest-drain.config.cjs)
כדי שיופיע ויהיה שליט כמו כל drain. drain_halacha_queue.py הובא לבקרת-גרסאות.
Invariants: מקיים G2 (הרחבת /operations + הגשר הקיים, לא מסלול מקביל) ו-G1
(drain_controls = מקור-אמת יחיד לכיבוי, נורמליזציה במקור ולא תיקון-בקריאה).
אין בליעת שגיאות שקטה (הגשר מחזיר {ok,error}; המוטציות מציגות toast).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The 211 open missing_precedents include 99 Supreme serial-format rulings
(בג"ץ/בר"מ/עע"מ NNNN/YY) with no נט-format triple — fetchable only from
supremedecisions.court.gov.il. Decoded its public JSON API (no browser, no
CAPTCHA, no smart-card); validated live on בג"ץ 3483/05 + בר"מ 10212/16.
- court_fetch_supreme.py: rewrite. POST Home/SearchVerdicts with a structured
`document` ({Year:"YYYY", CaseNum, OldMainNumFormat:true, SearchText:[…]}) +
X-Requested-With header → records; GET Home/Download?path=&fileName=&type=4 →
PDF. The earlier attempt failed only on the request shape (string vs object).
2-digit→4-digit year; try candidate docs best-first (פסק-דין→pages), skipping
the published-report 's'-prefix files the free endpoint WAF-blocks.
- orchestrator: on successful ingest, close matching open missing_precedents
(link to the new case_law). End-to-end validated (בר"מ 10212/16 → corpus).
- backfill_missing_precedents.py: enqueue fetchable open gaps (supreme + net)
into court_fetch_jobs; the drainer fetches+ingests+closes. dry-run default.
- X13 spec + SCRIPTS.md updated (Tier-0 decoded, no longer a limitation).
Very old un-digitized Supreme cases (e.g. בג"ץ 389/87 → 0 records) → manual.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Gemini key is stored in Infisical as GOOGLE_GEMINI_API_KEY
(nautilus /external-apis/gemini). Align the panel to read that canonical name
first, falling back to bare GEMINI_API_KEY for back-compat — so an
Infisical→.env sync keeps working.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The halacha extraction queue was stuck (same class as the metadata issue): 26
precedents requested extraction with no drainer, plus 1 orphaned in 'processing'
(status=processing, requested_at cleared → never re-picked by the queue).
- db.requeue_stale_processing_extractions(kind): re-stamp orphaned 'processing'
rows (requested_at IS NULL) so they re-drain; halacha extractor force=False
resumes from chunk checkpoints (no duplicates).
- process_pending_extractions calls it at the top — fully unattended, safe under
the global advisory lock. Mirrors the digests-drain self-heal.
- legal-halacha-drain.config.cjs: pm2 cron (every 2h, conservative — Claude is
slow/rate-limited and each run adds to the chair's pending_review queue).
drain_halacha_queue.py stays on claude_session (high reasoning quality for
holding/ratio; NOT moved to Gemini). SCRIPTS.md.
The chair-approval gate (INV-G10) is untouched — this only produces halachot;
Daphna still approves each in /approvals.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /precedents metadata queue was stuck — 24 rows requested, nothing draining
them — and the agentic claude CLI hit error_max_turns on what is a single
structured text→JSON task (slow + flaky). Metadata extraction is bounded
extraction, the wrong fit for an agentic loop.
- gemini_session.py: query_json drop-in (gemini-2.5-flash, JSON mode, httpx —
no new SDK dep). Reads GEMINI_API_KEY (~/.env; SoT Infisical
nautilus:/external-apis/gemini). Host-side only — no LLM from the container.
- precedent_metadata_extractor: claude_session.query_json → gemini_session.
Validated live: rich, accurate fields (case_name/summary/appeal_subtype/tags).
- process_pending_extractions: kind-aware cooldown — metadata 2s (Gemini, fast),
halacha keeps 30s (Claude rate limits).
- drain_metadata_queue.py + legal-metadata-drain.config.cjs (pm2 cron */15) so
the queue never clogs again. SCRIPTS.md.
- X8 INV-FP5 updated: per-task engine choice (Gemini=bounded metadata,
claude_session=agentic halacha), both host-side, single canonical queue (G2).
Agentic/voice-sensitive work (writing, analysis, halacha) stays on claude_session
(Daphna's subscription). Gemini cost ≈ $0.10/1M tokens — negligible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Periodic safety net for the multi-judge approval panel: samples panel-approved
halachot, re-runs the same 3-judge KEEP vote, and surfaces any that now lean
DROP — candidate false-keeps a human should glance at. Report-only by default;
--flag reopens flips to pending_review. Baseline 0/15 on the 2026-06-07 batch.
Closes the loop the literature prescribes (Trust-or-Escalate / selective
prediction): monitor the auto-decision error rate rather than trusting it
blindly. Reuses halacha_panel_approve's judges (single source of truth).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
drain_digests רץ תחת flock (drainer יחיד), אז כל שורה 'processing' בתחילת ריצה
היא שריד מריצה קודמת שנקטעה באמצע-שורה (סשן/מכסה). מאפסים אותה ל-'pending'
לריצה חוזרת — סוגר את הפער האחרון ל-resume אוטומטי מלא ללא התערבות.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The chair cannot review every pending halacha. Three independent-lineage judges
(Opus via claude_session · DeepSeek · Gemini-2.5-flash — #1 on LegalBench) vote
on the COARSE axis we proved reliable across models (92%): "is this a genuine,
keepable rule?". Only an agreed verdict acts; every split escalates to the chair
(INV-G10). Buckets: clean→KEEP?; nli_unsupported→entailment re-adjudication;
extraction-defects→re-extraction.
halacha_panel_calibrate.py calibrates the voting policy on the gold-set's
is_holding (the coarse label) per Trust-or-Escalate (ICLR 2025): unanimous →
94.9% precision / 78% coverage; majority → 92.9% / 99%; ZERO false-drops in
both (the panel never rejects a good rule). Chosen policy (chair-approved):
clean→majority-2/3, nli→asymmetric (majority-reject, unanimous-approve),
defects→re-extraction. Reversible (--apply backs up review_status+flags first).
Sources: Panel-of-LLM-Evaluators (PoLL) · Trust-or-Escalate (ICLR 2025,
arXiv:2407.18370) · selective-prediction / learning-to-defer.
Invariants: upholds G10 (human gate — splits escalate, panel only collapses the
queue) and G9 (provenance — reviewer records the panel + policy). Read paths only
in calibrate; --apply writes review_status/quality_flags reversibly with backup.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
COURT_FETCH_SHARED_SECRET + LEGAL_CHAT_SHARED_SECRET migrated to Infisical
nautilus:/legal-ai (2026-06-07). Updated the pm2 config comments: the stale
"migrate to Infisical once the MCP server is back" TODO is now done; local
env files remain the runtime source, Infisical is the SoT/record.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ה-cron של drain_digests הוא מנגנון ה-resume (pending-based, idempotent, host-side,
לא תלוי בסשן). חיזוק: אם enrich נכשל באמצע (מכסת claude נגמרה) השורה נשארה
'completed' עם שדות ריקים → לא היתה מטופלת שוב. עכשיו drain מאפס בתחילתו כל
digest 'completed' עם concept_tag ריק *וגם* underlying_citation ריק (= חילוץ
שמעולם לא נחת; שורה תקינה תמיד מכילה לפחות מראה-מקום) → pending לריצה חוזרת.
כך כל קטיעה/מכסה מתאוששת אוטומטית בריצת ה-cron הבאה.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>