Commit Graph

983 Commits

Author SHA1 Message Date
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
49cbd8bb3a Merge pull request 'fix(halacha): rate-limit refusal ≠ empty answer — לא checkpoint chunk בכשל (#144)' (#267) from worktree-halacha-no-empty-checkpoint 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 11s
2026-06-15 04:23:01 +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
ce86821393 Merge pull request 'fix(extraction): reconcile לתיקים-יתומים בתור — pending+requested_at=NULL (#139)' (#264) from worktree-extraction-orphan-reconcile into main
Some checks failed
G12 Leak-Guard / leak-guard (push) Has been cancelled
Lint — undefined names / undefined-names (push) Has been cancelled
Build & Deploy / build-and-deploy (push) Has been cancelled
2026-06-15 04:11:19 +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
ea232da92d Merge pull request 'fix(extraction): סינון cited_only מתור/מוני החילוץ (#140)' (#263) from worktree-cited-only-skip-queue into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m40s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 12s
2026-06-15 04:04:33 +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
e552d831bd Merge pull request 'fix(precedents): חילוץ מספר-תיק קנוני מהציטוט — לא ציטוט-מלא כמזהה (#137)' (#260) from worktree-case-number-from-citation into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 2m0s
G12 Leak-Guard / leak-guard (push) Successful in 7s
Lint — undefined names / undefined-names (push) Successful in 18s
2026-06-15 03:22:18 +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
1094ac9967 feat(halacha): ספי-עצירה-רכים לדריינר — 5-שעות 75% / שבועי 65% (עצירה לפני 429) (#259)
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 9s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
Co-authored-by: Chaim <chaim@marcus-law.co.il>
Co-committed-by: Chaim <chaim@marcus-law.co.il>
2026-06-15 03:18:56 +00:00
76a29756c5 Merge pull request 'fix(halacha): שחזור-עצמי לנעילת-advisory דלופה — לא לחסום חילוץ-הלכות (#142)' (#258) from worktree-halacha-lock-leak 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 11s
2026-06-15 02:57:45 +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
d7ef3e7f38 Merge pull request 'fix(halacha): fresh CLI 429 is ground truth over the usage endpoint (rate-limit churn)' (#257) from worktree-halacha-ratelimit-groundtruth into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 18s
G12 Leak-Guard / leak-guard (push) Successful in 3s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-15 02:28:51 +00:00
1340bff6f1 fix(halacha): a fresh CLI 429 is ground truth over the usage endpoint (rate-limit)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
PR #251 made the OAuth usage endpoint the PRIMARY rate-limit signal and the log
429 only a fallback for when the endpoint is unreachable. Observed 2026-06-15: the
endpoint reported the window <100% (available) while the claude CLI kept 429-ing
("session limit"). The supervisor then read 'rate_limited=false', classified the
drain 'hung', and restart-churned it — RE-EXTRACTING already-completed precedents
under the rate limit and DEGRADING them (e.g. 4624/21 lost halachot 3→1, only
4/18 chunks). delta_done went negative (completed cases reverting).

Fix: a FRESH CLI 429 is ground truth — the call is literally failing.
  • ENTER cooldown on EITHER signal (endpoint-exhausted OR fresh 429), so a 429
    overrides an endpoint that wrongly reports the window available.
  • VETO the early resume while a fresh 429 remains (the endpoint can lie
    "available" mid-storm → without the veto we'd bounce straight back to churn).
  • DEFAULT_COOLDOWN_MIN=30 when a fresh 429 has no parseable reset time.
While limited the drain STOPS (no 429-hammering, no degrading completed cases) and
re-ignites only once quota is back AND no fresh 429 remains.

Tested: 8 unit-tests over the decision matrix (endpoint×429×stored-cooldown),
incl. the exact tonight case and the veto. py_compile clean.

Immediate mitigation already applied out-of-band: drain stopped + disabled
(drain_controls.disabled) to halt the degradation until this deploys.

Invariants: G1 (fix at source — trust the failing call, not a lagging endpoint),
G2 (same cooldown path, no parallel control). Builds on PR #251.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 02:28:16 +00:00
7f9f502f29 Merge pull request 'fix(precedents): normalize citation→docket case_number + enforce source_type↔precedent_level' (#256) from worktree-precedent-casenum-sourcetype into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m51s
G12 Leak-Guard / leak-guard (push) Successful in 6s
Lint — undefined names / undefined-names (push) Successful in 17s
2026-06-14 20:57:34 +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
1a4b4fcf63 Merge pull request 'fix(metadata): accept GOOGLE_GEMINI_API_KEY in gemini_session — host metadata extraction was fully broken' (#255) from worktree-gemini-api-key-name into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 2m25s
G12 Leak-Guard / leak-guard (push) Successful in 9s
Lint — undefined names / undefined-names (push) Successful in 14s
2026-06-14 20:33:45 +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
6bc9fa89a2 Merge pull request 'feat(ui): פאנל אישור-תכניות — טאב /precedents + מרכז-אישורים (PR-B)' (#254) from worktree-plans-ui into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 42s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-14 15:28:22 +00:00
5d75d36e2a feat(ui): פאנל אישור-תכניות — טאב /precedents + מרכז-אישורים (PR-B)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 9s
הטמעת ה-UI למרשם-התכניות אחרי אישור-עיצוב ב-Claude Design (מוקאפ 22-plans-review).
- web-ui/src/lib/api/plans.ts: hooks (usePlansPending, usePlanDuplicates,
  useUpsertPlan, useUpdatePlan, useReviewPlan, useMergePlans) + טיפוס Plan מקומי.
- plans-review-panel.tsx: כרטיס-תכנית עם משפט-הציטוט הקנוני (כפי שייכתב בבלוק ט),
  שדות-תוקף, סימון חוסר-תאריך, באנר "כפילות אפשרית → מזג לכאן" (find_similar_plans,
  מיזוג ידני — G10), עריכה/הוספה inline עם תצוגה-מקדימה חיה של הציטוט.
- precedents/page.tsx: טאב "תכניות" + PlansPendingPill + deep-link tab=plans.
- web/app.py: href קטגוריית-התכניות במרכז-האישורים → /precedents?tab=plans.
- api:types: types.ts מחודש מ-openapi החי (5 נתיבי /api/plans).

מרכז-האישורים (/approvals) מרנדר קטגוריות גנרית — קטגוריית-התכניות (PR-A) מופיעה
אוטומטית. אימות: api:types ✓, tsc --noEmit ✓, lint exit=0 (ללא אזהרות חדשות).

Invariants: G10 (אישור אנושי + מיזוג ידני) · INV-AH (ציטוט דטרמיניסטי, תצוגה-מקדימה
תואמת format_plan_citation) · INV-IA (שער-אחד: טאב קיים + מרכז-אישורים, ללא עמוד חדש) ·
X6 (UI↔API, apiRequest + טיפוסים). עבר שער-העיצוב Claude Design (feedback_claude_design_gate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 15:27:36 +00:00
70ac888592 Merge pull request 'feat(plans): הרשאות-סוכנים + dedup/merge/edit + API לתור-אישור תכניות (backend)' (#253) from worktree-plans-followups into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m28s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 9s
2026-06-14 14:40:34 +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
09ea1ee599 Merge pull request 'feat(plans): מרשם-תכניות קנוני (V38) + נוסח-ציטוט אחיד דטרמיניסטי לבלוק ט' (#252) from worktree-plans-registry into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m37s
G12 Leak-Guard / leak-guard (push) Successful in 3s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-14 13:47:49 +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
83293ca619 Merge pull request 'fix(halacha): authoritative rate-limit detection + early-morning catch-up window (supervisor)' (#251) from worktree-halacha-supervisor-ratelimit into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 7s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-14 10:03:24 +00:00
49efa94d60 fix(halacha): authoritative rate-limit detection + early-morning catch-up window (supervisor)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
הדריינר רץ בלילה 13→14.6 אך חילץ 0 הלכות: מכסת-המנוי של claude.ai אזלה
(`api_error_status:429 "You've hit your session limit · resets 2:30am UTC"`,
`total_cost_usd:0`), וה-reset (5:30 IDT) נחת מעט אחרי סגירת החלון (05:00 IDT).
המתזמר סיווג זאת שגוי כ-"hung" ועשה restart-storm כל 15 דק' — כי `scan_rate_limit`
קורא רק 120 שורות-זנב, וה-429 (שורה 8273/9170) נקבר תחת ~900 שורות teardown שה-churn
שלו עצמו ייצר. בנוסף "hold" לא עצר את הדריינר → המשך הלמת-429 ובזבוז המכסה.

Fix A — זיהוי rate-limit עמיד:
  • `quota_exhausted()` חדש: מקור-האמת הוא endpoint-המכסה (`subscription_usage`,
    אותו util שה-UI מציג) — durable, לא תלוי בעומק-זנב-הלוג. log-scrape רק כ-fallback.
  • בזמן מוגבל עוצר דריינר online (`hold-stopped`) כדי לא להלום 429; מצית-מחדש
    כשהמכסה חוזרת (exit מיידי כש-endpoint <100%, או probe `claude -p` אם endpoint למטה).

Fix B — חלון catch-up בוקר [05:00–07:00 IDT):
  • נפתח רק לניקוי backlog שנותר כשהמכסה חזרה (מגודר: לא-מוגבל + תור≠ריק) כדי
    שהמכסה המשוחררת לא תתבזבז עד הלילה הבא. הקצה המורחב מועבר לדריינר (window self-guard).

נתונים בטוחים — תיקים נשארו 'processing' for retry, שום הלכה לא אבדה.
13 unit-tests עוברים (parse endpoint, gating של catch-band, win extension); `status` חי OK.

Invariants: מקיים G1 (תיקון-במקור: זיהוי ממקור-מכסה סמכותי, לא מתסמין-לוג),
G2 (אותו endpoint+מנגנון-חלון קיימים — בלי מסלול מקביל), INV-G3/X16 (לא נוגע
ב-checkpointing הדטרמיניסטי). G12 לא רלוונטי (host-side pm2, בלי Paperclip).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 10:02:35 +00:00
f8791ba4a1 Merge pull request 'ci: שער undefined-names (pyflakes) — שהבאג של PR #249 לא יחזור + תיקון NameError חבוי' (#250) from worktree-ci-undefined-name-guard into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-14 09:59:11 +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
1fbb1eede6 Merge pull request 'fix(api): שינוי-שם תיק מחזיר 500 — ניתוב Paperclip דרך שער-הפלטפורמה (INV-G12)' (#249) from worktree-fix-case-update-port into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 17s
G12 Leak-Guard / leak-guard (push) Successful in 3s
2026-06-14 08:38:22 +00:00
e2c94144d0 fix(api): route Paperclip title-sync/webhooks through the Port — 500 on case rename (INV-G12)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
PUT /api/cases/{n} crashed with 500 (NameError: 'paperclip_client' is
not defined) whenever the title changed. The G12 platform-port refactor
dropped the `paperclip_client`/`paperclip_api` module imports from
app.py but left three call sites still referencing the old module names:

  - app.py:2096  paperclip_client.update_project_name  (title sync → fires on every rename)
  - app.py:3806  paperclip_api.emit_export_complete_webhook
  - app.py:7390  paperclip_api.emit_missing_precedent_webhook

All three are background_tasks, so the latter two were latent NameErrors
waiting to fire. Expose the three functions through agent_platform_port
(the single Paperclip seam) and call them via the Port, restoring G12.

Invariants: upholds INV-G12 (all Paperclip access via agent_platform_port).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 08:37:51 +00:00
6fc87de1c5 Merge pull request 'feat(ops): פאנל "מתאמי-סוכנים" ב-/operations — מעבר-אדפטר בכפתור' (#248) from worktree-adapter-migrate-ui into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 2m0s
G12 Leak-Guard / leak-guard (push) Successful in 6s
2026-06-13 12:12:40 +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
b9e4c1fde4 Merge pull request 'feat(ops): מעבר-אדפטר בטוח לכל סוכן ← כל אדפטר (any→any) — סוגר FU-8a' (#247) from worktree-adapter-migrate into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 12s
G12 Leak-Guard / leak-guard (push) Successful in 4s
2026-06-13 11:36:56 +00:00
f7a8ad48ac feat(ops): מעבר-אדפטר בטוח לכל סוכן ← כל אדפטר (any→any, שתי החברות) — סוגר FU-8a
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
הקשר: החלפת adapter_type ב-dropdown הגולמי של Paperclip מקריסה את הסוכן מיד —
content_arg adapters (gemini_local/deepseek_local) שוברים את ה-frontmatter המוביל `---`
(yargs/arg-parser קורא אותו כדגל → "Not enough arguments following: prompt"), המודל
חייב להתאים ל-provider, ו-excludeTools של gemini גלובלי לכל הסוכנים. בנוסף — שינוי
חברה-אחת מפר את INV-MC1 (סנכרון מדלג על adapter mismatch → drift שקט).

הכלי מיישב את 3 צירי-הכשל data-driven, מעביר בשתי החברות יחד, הפיך מדויק, ומסרב
מעבר לא-בטוח ב-preflight (סוגר את פער FU-8a "אין שער-ולידציה למעבר-אדפטר").

- scripts/adapter_profiles.py — רישום-פרופילי-אדפטר (מקור-אמת יחיד; אדפטר חדש = רשומה אחת)
- scripts/migrate_agent_adapter.py — --check/--apply/--revert/--verify; frontmatter→.nofm.md
  נגזר, sidecar לשחזור, --relax-tools ל-excludeTools, PATCH דרך /api/agents/{id} (לא DB)
- .gitignore + scripts/SCRIPTS.md

נבדק: --verify (9 סוכנים תקינים), --check (CEO→gemini מציג nofm+model+tool-conflict;
provider-guard תופס מודל לא-תואם, exit≠0), ובדיקות-לוגיקה טהורות (strip/canonical/recompute).

Invariants: מקיים G2 (רישום+קובץ-קנוני יחיד, גרסת-gemini נגזרת), G12 (PATCH-API דרך
המעטפת, לא DB), INV-MC1 (שתי חברות יחד). סוגר FU-8a.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 11:28:16 +00:00
4dce06c04a Merge pull request 'fix(supervisor): gate + display weekly-Sonnet instead of weekly-Opus' (#246) from worktree-supervisor-sonnet into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 26s
G12 Leak-Guard / leak-guard (push) Successful in 9s
2026-06-13 10:58:23 +00:00
eac4dd3ac9 fix(supervisor): gate + display weekly-Sonnet, not weekly-Opus
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 8s
On this claude.ai account the populated per-model weekly cap is Sonnet;
seven_day_opus is null (no separate Opus cap). So quota_available() now gates on
five_hour + seven_day + seven_day_sonnet (was seven_day_opus, which never bound),
and `status` prints weekly-Sonnet. The all-models seven_day cap remains the
backstop for Opus usage regardless. Matches the /operations display (#245).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 10:58:05 +00:00
01bc1b9743 Merge pull request 'fix(operations): cache usage endpoint (avoid 429) + weekly-Sonnet instead of Opus' (#245) from worktree-usage-cache into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m48s
G12 Leak-Guard / leak-guard (push) Successful in 4s
2026-06-13 10:44:23 +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
17460044ac Merge pull request 'feat(operations): show real claude.ai subscription usage % on /operations' (#244) from worktree-operations-usage-ui into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m35s
G12 Leak-Guard / leak-guard (push) Successful in 5s
2026-06-13 10:35:18 +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
d093319ffd Merge pull request 'feat(supervisor): gate quota on the real claude.ai usage % (OAuth usage endpoint)' (#243) from worktree-supervisor-usage-endpoint into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 13s
G12 Leak-Guard / leak-guard (push) Successful in 6s
2026-06-13 10:19:46 +00:00
9e46db3c48 feat(supervisor): read real claude.ai usage % from OAuth endpoint for quota gating
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
The supervisor's quota check used a tiny `claude -p` probe to decide whether the
claude.ai subscription had room. That works but is indirect (an Opus-adjacent
round trip) and only answers yes/no. Anthropic exposes the actual utilization —
the same 5-hour / weekly / weekly-Opus percentages the Claude Code status bar
shows — via the (undocumented) GET /api/oauth/usage endpoint.

- subscription_usage(): reads the OAuth token from ~/.claude/.credentials.json
  and GETs /api/oauth/usage with the required `claude-code/*` User-Agent (without
  it the request hits an aggressively rate-limited bucket and 429s). Returns the
  parsed {five_hour, seven_day, seven_day_opus, ...} or None on any failure.
- quota_available(): now prefers the endpoint — a drain run resumes only when the
  5-hour, weekly, AND weekly-Opus windows are all <100% (the extractor runs Opus).
  More precise than the probe and sees every limit the way the UI does. Falls
  back to the `claude -p` probe when the endpoint is unreachable (it's
  undocumented and may change).
- `status` subcommand now prints the live percentages + reset times.

Note: this is the data/logic layer only. Surfacing the % on the /operations
page is a visual UI change and must go through the Claude Design gate first
(web-ui/AGENTS.md) — deferred.

Invariants: G1 (resume decision driven by the authoritative usage state).

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