Commit Graph

130 Commits

Author SHA1 Message Date
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
d4ec675c67 feat(plans): עיון+חיפוש בתכניות-מאושרות בטאב התכניות (Phase A)
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 הציג רק review_status=pending_review,
כך שתכניות מאושרות (התקינות, בשימוש-חוזר בבלוק ט) לא היו נגישות לעיון.
ה-backend כבר תמך ב-?review_status= ו-?q= — חסר רק חוט-UI.

- plans.ts: usePlansPending → usePlansAll (טעינת כל המרשם בקריאה אחת;
  הפאנל מסנן client-side — מרשם קטן, מונה-מדויק לכל סגמנט, חיפוש מיידי)
- plans-review-panel: סרגל-מצב (ממתינים/מאושרות/כולן) עם מונים + תיבת-חיפוש
  fuzzy (מספר/שם/ייעוד/aliases, מנורמל-bidi); הערת-קטיעה אם >1000 (בלי cap שקט)
- PlanCard מסתעף לפי review_status: מאושרת/נדחתה → תג-מצב + "החזר לתור"
  (review→pending_review) במקום אשר/דחה; דדופ-candidates רק בתור

עבר דרך שער-עיצוב Claude Design (מוקאפ 22-plans-review מאושר ע"י חיים).
ללא שינוי-backend. מרחיב מרשם-V38 הקיים — לא מסלול מקביל.

Invariants: G2 (יכולת קיימת, endpoint קיים) · INV-IA2 (שער-יחיד /precedents)
· G10 נשמר (review_status שער-יו"ר).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 10:05:15 +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
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
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
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
9154a1d817 feat(operations-ui): BURST toggle + deadline dialog for the halacha drain (#133)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 7s
Implements the chair-approved Claude Design mockup (02b-operations-burst): a single
gold " הפעל BURST" button on the legal-halacha-drain row that opens a deadline
dialog (datetime-local, default the upcoming Saturday 18:00, quick chips for Sat
18:00 / +5h / midnight). While a burst is active the row shows a gold pill with the
deadline + an "עצור BURST" stop button. Manual only.

- operations.ts: burst_until on OpsService + useDrainBurst hook (POST .../burst).
- page.tsx: BurstControl component, gated to legal-halacha-drain.
- types.ts: regenerated (npm run api:types) — the new burst route.

Active-state is derived from burst_until presence (the supervisor NULLs it at the
deadline, snapshot refetches every 5s) — no impure Date.now() in render.

Invariants: G1/G2 — single DB-backed control (drain_controls.burst_until), shared
with the host supervisor; no parallel path. UI passed the Claude Design gate.
2026-06-12 11:46:41 +00:00
309064be7d chore(api-types): regenerate types.ts — add /learning-status + re-sync prod drift
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Picks up GET /api/cases/{case_number}/learning-status (PR #233) plus accumulated
schema drift from prior backend changes that never regenerated types.ts. tsc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 10:57:49 +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
f0a8af30dc feat(ui): תור-אישור הלכות מאוחד — 2 תצוגות לפי פעולה (#133)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
מבטל את ה-toggle "תור נקי / דורש תיקון-חילוץ" שבו "תור נקי" ריק
לגמרי (כל ההלכות-הנקיות נפתרו), והעבודה האמיתית חבויה מאחורי
הכפתור השני שגם מערבב התלבטות-פאנל עם פגמי-חילוץ. אושר ב-Claude
Design (כרטיס 19-halacha-queue-unified).

במקום זה — תור אחד, fetch אחד, פיצול client-side לפי **סוג-הפעולה**:
- "להכרעתך" = הלכות שהפאנל דן בהן (יש panel_round) או נקיות →
  אשר/דחה, עם טבלת-ההתלבטות; ממוין פיצול-פאנל-תחילה (FU-3).
- "דורש תיקון-חילוץ" = מסומנות-דגל שלא עברו התלבטות → תיקון-חילוץ.

`useHalachotPending` אוחד לקריאה אחת (exclude_low_quality=false +
order_by_priority + cluster + include_equivalents + include_panel_round);
נוסף `isExtractionFixItem(h)` (= !panel_round && יש דגל). PendingPanel
מפצל ב-useMemo, segmented-control עם מוני שני הדליים. אפס שינוי-backend
(הפרמטרים כבר קיימים מ-#220/#222).

display-only, שער-אישור יחיד (INV-IA/G10). ולידציה: tsc + eslint נקי.
חלק מ-#133.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 07:29:39 +00:00
6fba565fcb fix(ui): תרגום עברית לסטטוסי-תיק in_progress + qa_failed
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
הבאג: ה-StatusBadge מציג מחרוזת גולמית באנגלית ("in_progress") כשהשרת
פולט סטטוס שאינו במפת-התוויות. CaseStatus ב-web-ui החסיר שני סטטוסים
שהשרת אכן פולט — in_progress (workflow.set_outcome) ו-qa_failed
(app.py human-gate) — ולכן נפלו ל-fallback `?? status` (אנגלית גולמית).

התיקון (יישור frontend↔backend SoT, X6 UI-API contract):
- CaseStatus type: הוספת "in_progress" + "qa_failed".
- status-badge.tsx: 4 מפות Record<CaseStatus> — LABELS (בעבודה / בדיקת
  איכות נכשלה), ICONS (Hammer / AlertTriangle), DESCRIPTIONS, TONE
  (warn / danger).
- status-donut.tsx: GROUP_OF — in_progress→intake, qa_failed→writing.

ללא שינוי-עיצוב ויזואלי (תיקון-תוכן/i18n של רכיב קיים) → חוסה תחת
החריג המפורש בשער-העיצוב ב-web-ui/AGENTS.md.

invariants: מקיים X6 (UI↔API contract — הטיפוס תואם לסטטוסי-השרת);
לא G2 (אין מסלול מקביל), לא G1-symptom (מתקן את מקור-הדריפט בטיפוס).
tsc --noEmit עובר נקי.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 06:57:18 +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
b0411db80b fix(precedents): deferred (snooze) לא נספר כ"ממתין" ולא צובע שורה אדום
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
המשך ל-#215. תור-הסקירה האמיתי (list_halachot) מסנן pending_review בלבד —
deferred ("נדחה למועד", #84 snooze) מוסט במכוון מהתור הפעיל. לכן ספירתו
כ"ממתין" צבעה שורות אדום על עבודה שדפנה כבר הסיטה הצידה — בדיוק ההטעיה
ש-#215 בא לתקן.

- backend: pending_count = pending_review בלבד (היה pending_review+deferred);
  deferred_count חדש ונפרד. אותה שאילתה, מקור-אמת יחיד (G2).
- UI: deferred מוצג כמקטע מושתק (⏸ N) רק כשקיים — לא צובע אדום, לא נספר
  בממתינות. הצבע האדום + רקע-השורה מונעים מ-pending_count (=pending_review)
  בלבד, בעקביות עם התור.

Invariants: G2 (ספירה ממקור-אמת יחיד תואמת-תור). שינוי-UI לפי החלטת היו"ר.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 04:40:26 +00:00
4ea6326766 fix(precedents): פיצול תג-הלכות ל-מאושר/ממתין/נדחה + הדגשת שורות-טיפול
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
התג בספריית-הפסיקה הציג "approved/total" — total כלל גם הלכות שנדחו,
כך ש-17/27 נקרא כאילו 10 ממתינות בעוד שבפועל הן נדחו. כעת:

- backend: list_external_case_law מחזיר pending_count (pending_review+deferred)
  ו-rejected_count לצד approved_count (approved+published). מקור-אמת אחד
  לספירה, אותה שאילתה — אין מסלול מקבילי (G2).
- UI: התג מציג 17/0/10 (מאושר/ממתין/נדחה), צבעי-משמעות, tooltip מפרש.
  ממתין נצבע אדום-בולט רק כשגדול מ-0.
- UI: שורה עם הלכות ממתינות (pending_count>0) מקבלת רקע אדמדם דרך
  rowClassName() — חל על שורות פסיקה ועל שורות ועדות-ערר.

Invariants: G2 (ספירה ממקור-אמת יחיד, ללא מסלול מקבילי). שינוי-UI ויזואלי
לפי אפיון מפורש של היו"ר (שער-עיצוב).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 04:29:07 +00:00
b447ffb184 fix(ops): ייבוש backlog-רפאים של חילוץ-מטא — נרמול-במקור של metadata_extraction_status (G1)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 9s
מונה "ממתין (בקלוג)" ב-/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>
2026-06-11 22:09:38 +00:00
6e69c1dc38 feat(ia): IA גל-2 — איחוד-משטחים: ערוץ-למידה אחד · /operations⊇/diagnostics · MET-2/3 (#131, X17)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 9s
גל-2 מבקלוג #127 — איחוד-משטחים לפי משטח-היעד של X17. מקיים INV-IA1/IA3/IA4 +
דלתות-הספ (X6 INV-UI7/8, 07-learning §0.4, 00-constitution G2). שומר G10/INV-LRN1
(לא הוסר שום שער-אנושי — רק שער/דגל כפול).

א) תיבת-אישור אחת (INV-IA1): כרטיסי "אישור הלכות"+"פסיקה חסרה" ב-/operations
   מצביעים ל-/approvals (לתיבת-האישורים ←) — /operations מנטר, /approvals מחליט.

ב) ערוץ-למידה אחד (INV-IA3): הוסר applied_to_skill end-to-end —
   - UI: כפתור "סמן כ'אומץ'" + badge "אומץ" ב-lessons-tab; badge ב-curator-portrait.
   - API: LessonPatch, _lesson_to_json, patch call, curator recent_findings (→review_status).
   - db.py: list/add/update_decision_lesson לא בוחרים/כותבים applied_to_skill;
     הפרמטר הוסר. העמודה+אינדקס נשמרים (back-compat, ללא migration), מסומנים DEPRECATED.
   - types: DecisionLesson/LessonPatch/CuratorFinding.
   review_status='approved' = הסטטוס היחיד "זורם-לכותב" (INV-LRN1, #126).

ג) MET-2/3 lost-update (INV-IA3): _append_methodology_override רץ עכשיו בטרנזקציה
   אחת עם SELECT ... FOR UPDATE — אין read-modify-write מתפצל מול עורך-המתודולוגיה
   או promote מקביל. /methodology = העורך-הקנוני; promote מבטל את ה-cache (גל-1 MET-1).

ד) /operations⊇/diagnostics (INV-IA4): גוף /diagnostics חולץ ל-<SystemHealthSection/>
   ומורנדר ב-/operations תחת "בריאות-מערכת". /diagnostics → redirect ל-/operations.
   /diagnostics הוסר מהניווט. משטח-ניטור יחיד.

ה) דלתות-ספ (≥3 מקורות ב-X17, אושר ע"י חיים /goal):
   - X6: INV-UI7 (aggregate=SSoT, mutation מבטל queryKey) + INV-UI8 (render-or-remove, חלקיות).
   - 07-learning §0.4: שער-אחד + טרנזקציה-אחת + applied_to_skill מוסר.
   - 00-constitution G2: תאום-המתודולוגיה כהפרה-ידועה-ממותנת.
   - X17 דלתות-ספ סומנו  קודדו.

בדיקות: py_compile app.py + db.py ✓ · tsc --noEmit ✓ · eslint ✓ (לבד מ-learning-panel:109
קיים-מראש). next build נכשל ב-worktree רק בגלל symlink (Turbopack) — Docker/CI תקין.
api:types יתרענן בדפלוי (curator/lessons אינם response-modeled; הטיפוסים יד-כתובים עודכנו).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 21:04:57 +00:00
36bae6c592 fix(ia): IA גל-1 — סנכרון-cache + נתונים-שגויים + מחיקת-מתים (#130, X17)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 13s
גל-1 מבקלוג #127 (docs/ia-audit-redesign.md §4) — תיקון מקומי, ללא הגירת-IA.
מקיים G2 בשכבת-ה-UI דרך INV-IA1/IA2/IA5/IA6 (docs/spec/X17).

א) פערי-סנכרון (INV-IA2 — mutation מבטל כל קורא):
- CAS-1/2: העלאת-DOCX/export מבטלים ['decision-blocks'] (מחוון source_of_truth)
- APR-1/4: פתרון/יצירת-הערה מבטלים ['chair','pending'] (תיבה+תג-סרגל)
- APR-5/ADM-2: אישור/batch הלכות מבטלים ['chair','pending']+['operations']
- APR-6/ADM-3: create/update/delete/upload פסיקה-חסרה מבטלים שניהם
- LRN-6: ComparePanel גוזר בחירה מהקורפוס המרוענן (אין POST ל-id מחוק → 404)
- LRN-8: מחיקת-קורפוס מבטלת רשימת-צ'אטים (chat שהתייתם לא נשאר עם קישור-קורפוס תקוע)
- LRN-10/MET-1/MET-8: promote מבטל גם lessons וגם methodology (LessonsTab+/methodology)

ב) נתונים-שגויים (INV-IA5 — סטטוס מגובה-צרכן):
- LRN-4: KPI "דפוסי סגנון" — הוסר היחס-השקרי "מתוך total_patterns" (שאילתות עצמאיות)
- LRN-5: findings_applied (דגל אינפורמטיבי-בלבד) → findings_approved (שער INV-LRN1 האמיתי)
- ADM-1: halacha_backlog שהוחזר ונזרק → מרונדר ב-/diagnostics, מצביע ל-/approvals (INV-IA1)
- ADM-6: מוני-סוכנים מסמנים "חלקי+" כשחברת-Paperclip לא נטענה
- APR-3: מכוסה ע"י APR-1 (count+sample מאותה שאילתה; הבעיה היתה staleness-cache)
- MET-6: עורך-צ'קליסטים מציג איזה case בוחר כל צ'קליסט (explainer-תחולה)
- ADM-5: ערך-Container מסומן "ממתין ל-redeploy" כש-Coolify≠Container

ג) מתים/jargon:
- PRE-2: הוסר GET /api/precedent-library/queue/pending (אפס צרכני-frontend)
- PRE-3/5: AuthorityBadge (binding/persuasive) מרונדר גם בחיפוש, לא רק בתור-הביקורת
- MET-5: הוסר ז'רגון T7/T15 מטקסט-העזר ב-/methodology (INV-IA6)

Invariants: מקיים INV-IA1/IA2/IA5/IA6 (X17), G2 (מקור-אמת יחיד בשכבת-UI), G10
(לא הוסר שום שער-אנושי — רק סנכרון/נתון/קוד-מת). שומר INV-LRN1.

בדיקות: py_compile web/app.py ✓ · tsc --noEmit ✓ · eslint ✓ (לבד מ-learning-panel:109
unescaped-quote — קיים-מראש ב-main, מחוץ לסט-הממצאים). next build נכשל רק בגלל
symlink node_modules ב-worktree (Turbopack) — ה-build ב-Docker/CI תקין.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 20:47:32 +00:00
8651529327 feat(ui): דף /scripts — קטלוג סקריפטים read-only מ-SCRIPTS.md
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 8s
מגיש את 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>
2026-06-11 19:42:44 +00:00
4b01283e3b feat(learning): שער-אישור ל-decision_lessons — רק לקח מאושר זורם לכותב (INV-LRN1, #126)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 12s
אודיט #122 חשף שלקחי-הפאנל (decision_lessons) זרמו לכותב אוטומטית
(block_writer → get_recent_decision_lessons) ללא סינון-אישור — הפאנל כתב,
והכותב צרך מיד, בעקיפת שער-היו"ר (INV-LRN1/G10). מנגד, מה שהיו"ר אישר ב-promote
הלך לערוץ נפרד (appeal_type_rules). תוצאה: דליפה — תוכן לא-מאושר השפיע על הכתיבה.

התיקון — שער-אישור מפורש:
- עמודת review_status (proposed|approved|rejected) ל-decision_lessons (SCHEMA_V34).
- get_recent_decision_lessons (צרכן-הכותב) מחזיר רק review_status='approved'.
- הפאנל (style_lesson_panel) כותב 'proposed' (ברירת-מחדל) → לא זורם עד אישור.
- לקח שהיו"ר מקליד ידנית ב-/training = 'approved' מיידית (מדלג על שער-ההצעה).
- UI (lessons-tab, טאב "קורפוס" ב-/training): תג-סטטוס + כפתורי אשר/דחה/בטל-אישור.

הכרעת-יו"ר (2026-06-11): כל הלקחים שקדמו לשער (41) מתאפסים ל-'proposed' —
שום לקח לא זורם עד אישור מפורש (ברירת-המחדל של העמודה מיישמת זאת על הקיימים).

Invariants:
- INV-LRN1 / G10 (מקיים) — עדכון-ידע לערוץ-הכותב דורש אישור-יו"ר מפורש; אין auto-commit.
- INV-LRN5 (נשמר) — substance ממילא מסונן בפאנל; השער הוא על style_method בלבד.
- G1 (מקיים) — סינון-במקור (get_recent) ולא תיקון-בקריאה אצל הכותב.
- G2 (מקיים) — אותו פנקס decision_lessons; אין מסלול מקביל.

api:types: להריץ npm run api:types אחרי deploy (review_status נוסף ל-payload;
הטיפוסים הידניים ב-training.ts כבר מעודכנים, tsc עובר).

ref: #122 · #126 · data/audit/learning-loop-activity-20260611.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 18:13:59 +00:00
2f094b8d84 feat(operations): מסך "סוכנים פעילים" + ניהול ריצות (live-runs/log/cancel) (G12/X15, #119)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 6s
פאנל ב-/operations שמראה אילו סוכני Paperclip עובדים כעת (רצים+בתור), הפלט החי
שלהם, ושליטה מבוקרת: עצירת ריצה, איפוס session. סוגר את הנקודה-העיוורת שבה drain
מונע-סוכן (למשל ריקון תור הלכות ע"י ה-CEO heartbeat) עוקף את בקרת /operations
שמכירה רק שירותי pm2, והפלט הגולמי נגיש רק ב-Paperclip UI.

מקור-נתונים: Paperclip heartbeat-runs API (אומת חי):
  GET  /api/companies/{cid}/live-runs        — רצים+בתור (agentName/status/issue/outputSilence)
  GET  /api/heartbeat-runs/{id}/log          — NDJSON של פלט הסוכן
  GET  /api/heartbeat-runs/{id}/events        — timeline
  POST /api/heartbeat-runs/{id}/cancel        — עצירה מבוקרת (לא kill — מכבד watchdog+checkpoint)
  POST /api/agents/{id}/runtime-state/reset-session

ארכיטקטורה (G12/INV-PORT1): כל המגע החדש עם Paperclip דרך השער בלבד —
web/paperclip_client.py (shell) → re-export ב-web/agent_platform_port.py →
web/app.py צורך מהשער. leak_guard.py עובר (seam שלם). אסור kill ישיר על
process_pid (עוקף את השער).

Backend:
- paperclip_client: list_live_runs / get_run_log / get_run_events / cancel_run / reset_agent_session
- agent_platform_port: re-export pc_list_live_runs / pc_get_run_log / pc_get_run_events / pc_cancel_run / pc_reset_agent_session
- app.py: GET /api/operations/agents (אגרגציה CMP+CMPA, עמיד לכשל-חברה),
  GET .../runs/{id}/log, GET .../runs/{id}/events, POST .../runs/{id}/cancel,
  POST .../agents/{id}/reset-session

Frontend: פאנל "סוכנים פעילים" ב-/operations (polling 4s) + dialog ללוג חי
(פרסור NDJSON→טקסט קריא) + כפתורי עצור/אפס. הוספת hooks ל-operations.ts.

בטיחות: cancel על דריינר הלכות בטוח — חילוץ checkpointed per-chunk + resumable
+ self-heal לשורות processing.

Invariants: מקיים G12/INV-PORT1 (שער-הפלטפורמה). נוגע X6 (UI↔API).
api:types יורץ אחרי deploy (openapi.json חי).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 13:26:30 +00:00
e5168fe79d refactor(web-ui): disambiguate Paperclip agent DTO; document platform-presentation boundary (R3, G12, #112)
ממצא: התוכנית המקורית (namespace ל-paperclip.* ב-types.ts) בלתי-ישימה — types.ts
נוצר-אוטומטית מ-OpenAPI ("Do not make direct changes"); הפניות-Paperclip שם רק משקפות
את ה-API של ה-backend, ונשלטות ע"י מודלי-ה-Pydantic, לא ע"י הפרונט. הפרונט אינו
שכבת-אינטליגנציה — הפניות-Paperclip בו הן הצגת-נתוני-פלטפורמה (activity feed, קישור
לדאשבורד, סטטוס-ארכוב) או UI-ניהול מוצהר (paperclip-tab/agents-tab) — כולן shell-adjacent
לגיטימי תחת G12.

הבעיה האמיתית-והישימה: התנגשות-שם — `PaperclipAgent` הוגדר פעמיים עם shapes שונים
(config ב-paperclip-agents.ts מול activity ב-agents.ts). פוצל: ה-activity-DTO →
`PaperclipAgentStatus`; ה-config שומר `PaperclipAgent`. + הערת-כותרת שמסמנת את agents.ts
כמודול הצגת-פלטפורמה מוצהר.

מזין את R4 (#113): leak-guard חייב להחריג קבצים-נוצרים (types.ts) ולא לכלול את הפרונט
בהיקף שכבת-האינטליגנציה המוגנת.

אימות: tsc --noEmit נקי; eslint נקי על הקבצים ששונו.

Invariants: G12 (גבול-פלטפורמה מוצהר בפרונט), G2 (הסרת שם-טיפוס כפול-משמעות).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 09:33:44 +00:00
d4d2ab4d68 feat(arguments): פופאפ פרופוזיציות גולמיות בלחיצה על "מסתמך על N"
הקישור טיעון↔פרופוזיציות כבר נשמר ב-DB (legal_argument_propositions),
אך ה-UI הציג רק את המספר. מעשיר את get_legal_arguments באותו round-trip
(JOIN ל-claims) להחזיר supporting_propositions = {id, text, source_document},
ועוטף את שורת "מסתמך על N פרופוזיציות" ב-Popover שמציג את הטענות הגולמיות
verbatim עם מקור. שקיפות ועקיבוּת מהטיעון המאוגד חזרה לטענות-המקור.

- supporting_claims נשאר id-only (תאימות לאחור: מונה, צרכני MCP)
- supporting_propositions שדה חדש אופציונלי; fallback לטקסט סטטי כשחסר
- אין מסלול מקביל (G2) — העשרה של אותו endpoint; נרמול-במקור (G1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:51:09 +00:00
98c5feff25 feat(cases): תצוגת "פסיקה שצוטטה בהחלטה" בעמוד-התיק + שחזור חיווט-הרמס
UI שביקש חיים: בכניסה להחלטה רואים את הפסיקה שצוטטה בתוכה — מקושרת לספרייה
(קליק → /precedents/[id]) מול חסרה (סומנה אוטומטית להעלאה).

- web/app.py: GET /api/cases/{case}/citations — מהשורה internal_committee של
  ההחלטה ב-case_law → precedent_internal_citations: linked (join case_law) +
  missing (unresolved + האם flagged ב-missing_precedents).
- web-ui: lib/api/citations.ts (hook) + CitationsSection ב-drafts-panel
  (מוצג כשההחלטה בספרייה). מקושרת=ירוק/קליק, חסרה=ענבר "סומנה להעלאה".
- scripts/curator_apply_pipeline_branch.py: מקור-אמת לחיווט-הכפתורים של הרמס
  (ה-prompt חי רק ב-Paperclip DB). מקדים branch שמריץ את pipeline-ה-final
  ל-wake reason final_learning_*/final_halacha_* (HOME/DOTENV/DATA_DIR מוחלטים
  → מפתחות DeepSeek+Gemini + DATA_DIR נפתרים נכון). idempotent, שני הסוכנים.
  כבר הוחל ב-DB; הסקריפט לשחזור אחרי reset.

אומת: py_compile ✓ · tsc ✓ · החיווט אומת חי על 8126 (deepseek+gemini, dedup,
✓ pipeline הושלם). G2 (יכולת חסרה) · INV-LRN1/G10 נשמרים.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:59:21 +00:00
55362bf5a1 feat(learning): כל החלטה שלנו נכנסת תמיד לספריית-הפסיקה + בדיקת-ציטוטים אוטומטית
סוגר את הפער שלולאת-צמיחת-הקורפוס (07-learning §1.3) הוגדרה אך לא חווטה: מסלול
/final/upload הכניס רק לקורפוס-הסגנון, וההכנסה ל-case_law הייתה best-effort
שקטה שנכשלה כש-chair_name ריק.

web/app.py — /api/cases/{case}/final/upload עכשיו, סינכרונית:
- קובע chair_name דטרמיניסטית (תיק → ברירת-מחדל-ועדה לפי prefix; לעולם לא ריק →
  אילוץ case_law_internal_chair_check תמיד מסופק). לא נשען על חילוץ-LLM —
  להחלטות שלנו היו"ר ידוע.
- מכניס את ההחלטה ל-case_law כ-internal_committee (תמיד, לא best-effort) →
  ברת-ציטוט בהחלטות עתידיות. מטה-דאטה נוסף מועשר אסינכרונית (Gemini).
- מחלץ את הציטוטים שההחלטה מצטטת (extract_internal_citations), ו**מסמן
  אוטומטית** כל ציטוט שאינו בספרייה כ-missing_precedent (open) — dedup מול קיימים.
- התוצאה מוחזרת ב-response (enrolled/linked/missing_flagged) — לא נבלעת בשקט.

הציטוטים-המקושרים מזינים את לולאת-ה-corroboration (X11) — תוקן הניתוק שבו
החלטות שלנו לא היו ב-case_law ולכן לא חיזקו הלכות.

web-ui — toast מציג "נוספה לספרייה · N ציטוטים · M חסרים סומנו".
ספ: 07-learning §0.6 עודכן. אומת ידנית על בל"מ 8126-03-25 (15 קושרו / 6 סומנו).

Invariants: INV-LRN4, X11; G2 (יכולת חסרה, לא מקבילה); feedback_silent_swallow
(כשל-הכנסה צף, לא נבלע); DM7 (סמכות נגזרת).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 11:27:55 +00:00
9ae49f0f70 Merge pull request 'feat(learning): מסלול נקי להעלאת החלטה סופית + פאנל-סגנון דו-סוכני (DeepSeek+Gemini)' (#158) from worktree-final-upload-pipeline into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 42s
2026-06-08 09:04:16 +00:00
f79c46a352 feat(learning): מסלול נקי להעלאת החלטה סופית + פאנל-סגנון דו-סוכני (DeepSeek+Gemini)
מוסיף מסלול ייעודי לקליטת ההחלטה החתומה של היו"ר, ומפעיל אותו דרך שני
שלבים אוטומטיים מדורגים עם פאנלי-סוכנים (אוטו-אישור + אסקלציה ליו"ר).

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>
2026-06-08 09:03:26 +00:00
638eef6803 feat(ops): /operations — מוני-תור אחידים, "מה רץ עכשיו", וניהול-תהליכים
הדף הציג את התורים באופן לא-אחיד (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>
2026-06-08 08:57:23 +00:00
5745d36bb4 feat(digests-ui): publication filter + 'מאמר'/source badges for bulletins
משלים את #154 בצד-לקוח:
- פילטר "מקור" בדף /digests (כל המקורות / כל יום / עו"ד על נדל"ן) — backend:
  list_digests + /api/digests מקבלים publication.
- DigestCard: תג "מאמר" ל-digest_kind='article', ו-chip מקור לפרסום שאינו 'כל יום'.

build (webpack) עובר, lint נקי. digests = hand-written types (אין api:types).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 08:14:23 +00:00
54948eb8ab feat(nav): הסרת דף מדגם-זהב (goldset) מה-UI
הכיול החד-פעמי של ולידטורי חילוץ-ההלכות (#81.8) הסתיים — הוסר מה-UI:
- web-ui/src/app/goldset/page.tsx (הדף)
- web-ui/src/components/goldset/goldset-panel.tsx (הרכיב)
- web-ui/src/lib/api/goldset.ts (מודול ה-API)
- הקישור "מדגם-זהב" מתפריט "פסיקה" + השטחת התת-כותרת "ניתוח וכיול"
  (נותר רק "מפת הקורפוס" → רשימה שטוחה)
- ניקוי אזכורי gold-set מהערות approvals/page.tsx ו-chair.ts

ה-backend נשאר במכוון: טבלת halacha_goldset, ה-endpoints (/api/goldset*)
ופונקציות ה-DB משמשים את סקריפטי ה-eval/benchmark ומחזיקים נתוני-תיוג
אנושיים — אין מחיקת DB ואין שבירת סקריפטים. /api/chair/pending ממילא לא
כלל goldset, אז אין קישור שבור במרכז-האישורים.

Invariants: G2 (הסרת יכולת-UF מיותרת ללא יצירת מסלול מקביל).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:32:48 +00:00
34d80a39e5 feat(ops): /operations dashboard — everything running in the background
A single live page for all the background work that downloads/analyses, so the
chair can see what's running instead of guessing.

- court_fetch_service: GET /pm2 (unauthenticated, host-only) → trimmed pm2 jlist
  for the legal-* services (status, restarts, mem, cron schedule).
- FastAPI GET /api/operations: aggregates the DB-backed pipelines (court_fetch
  jobs, metadata + halacha extraction queues, halacha review gate,
  missing_precedents, digests, recent court ingests) and proxies the host /pm2
  over the docker bridge (graceful if the host service is down).
- web-ui /operations page (+ src/lib/api/operations.ts hook, nav entry under
  admin): services grid (with Hebrew labels + schedules) + pipeline cards +
  recent-fetch / recent-ingest lists. Auto-refreshes every 5s.

tsc --noEmit clean; pm2 status carries nothing sensitive and the bind
(10.0.1.1) is host/container-only, so /pm2 needs no secret.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 07:28:41 +00:00
d895062b4c feat(digests-ui): digest_kind badge — mark announcement issues in /digests
משלים את #141 בצד-לקוח: שדה digest_kind ב-Digest type (hand-written), ותג
"עדכון" ב-DigestCard לגיליונות announcement (לא-הכרעות). decision = ברירת-מחדל
ללא תג. זורם דרך /api/digests (digest_kind כבר ב-_DIGEST_COLS).

build (webpack) עובר, lint נקי בקבצי digests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 06:06:53 +00:00
7fb5134580 chore(web-ui): regenerate api types from prod OpenAPI
`npm run api:types` — brings the generated src/lib/api/types.ts up to date
with the live FastAPI schema (UI1: types derive from the OpenAPI SSoT). The
file had drifted; this regen captures the corpus-graph endpoints/models
(/api/graph/corpus, /api/graph/facets, /api/graph/node/{id}/neighborhood;
CorpusGraph / GraphNode / GraphFacets) plus accumulated changes from other
merged work. web-ui build passes against the regenerated types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 05:15:43 +00:00
ef21cb93e5 feat(graph): halacha (rule) layer (corpus graph — closes Phase 2)
Enables the previously-disabled "הלכות" toggle. Each approved/published halacha
of a displayed precedent becomes a hal:<id> node linked to its parent
precedent (extracted_from); two cross-rule edges when both endpoints are in
view: corroborates (a later ruling cites the rule —
halacha_citation_corroboration) and equivalent (same principle from another
committee — equivalent_halachot). Node size = corroboration in-degree.

Backend (web/graph_api.py — read-only, G2):
- _halacha_nodes_and_edges(): halachot WHERE case_law_id in view AND
  review_status IN (approved, published), LIMIT 600; rule_type carried in the
  source_kind slot, rule_statement in note. Wired into both build functions
  (gated via node_types). Metrics still exclude halacha edges (only cites/
  precedent-typed feed PageRank). Validated: 185 halachot on the top-30
  precedents; 20 corroboration + 5 equivalent edges in the corpus.

Frontend:
- graph.ts: GraphEdgeType += extracted_from.
- graph-filter-panel: "הלכות" toggle enabled (was disabled "שלב ב׳").
- graph-canvas: amber halacha nodes; edge colours — extracted_from (faint
  amber), corroborates (amber), equivalent (violet).
- graph-node-panel: halacha branch — אזכורים + סוג כלל + rule text; "open in
  library" deep-links to the parent precedent.
- graph-view: halacha added to node + edge legends.

web-ui build + lint pass. Invariants: G2 (SELECT-only), UI2 (no model change —
reuses note/source_kind/case_law_id slots).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 05:13:09 +00:00
fc5d69902f feat(graph): daily-digest (יומון) discovery layer (corpus graph PR E)
Chaim's idea: surface the downloaded "כל יום" digests in the graph. Each digest
COVERS the ruling it analyses — a corpus precedent when we have it (16), or a
synthesized gap node from its underlying_citation when we don't (269). So the
digest layer doubles as a discovery signal: it makes visible that the daily
feed overwhelmingly covers rulings NOT yet in the corpus.

Backend (web/graph_api.py — read-only, G2):
- "digest" added to VALID_NODE_TYPES (off by default).
- _digest_nodes_and_edges(): dig:<id> nodes from completed digests, `covers`
  edge → cl:precedent (linked_case_law_id in view) or → gap:<underlying_citation>
  (synthesized, deduped against the gap layer — real in-degree wins). Carries
  concept_tag (label), headline_holding (note), underlying_court/date.
- _add_digests() appends the layer with gap dedup. Wired into both build
  functions. GraphNode += note, digest_id. Gated via node_types (no app.py
  change). Validated: 16 covers→precedent, 269 covers→gap.

Frontend:
- graph.ts: GraphNodeType += "digest"; GraphEdgeType += "covers"; node fields.
- graph-filter-panel: toggle "יומונים (כל יום)" (off by default).
- graph-canvas: digest = teal node (r=4); `covers` edges teal.
- graph-node-panel: digest branch — concept + holding + court/date + link to
  /digests.

web-ui build + lint pass. Invariants: G2 (SELECT-only), UI2. api:types post-deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:31:04 +00:00
9a126f7c36 feat(graph): research-gap (ghost) nodes (corpus graph PR C)
Turns the graph into a gap-finder: the 247 unresolved internal citations
(a corpus precedent cites a ruling NOT in the corpus) collapse to 230 distinct
"gap" nodes — each sized by how many corpus precedents cite it, i.e. the
most-wanted missing precedent.

Backend (web/graph_api.py — read-only, G2):
- "gap" added to VALID_NODE_TYPES (NOT default → off unless requested).
- New _gap_nodes_and_edges(): gap:<normalized citation> nodes from
  precedent_internal_citations WHERE cited_case_law_id IS NULL, sized by global
  in-degree; cites edges only from precedents present in the view (dangling-edge
  invariant holds). Best-effort enrichment from missing_precedents via exact
  normalized-citation match → gap_status + missing_precedent_id. Validated:
  230 gaps, top ע"א 3213/97 (cited 5×), 230/230 matched to missing_precedents.
- GraphNode += gap_status, missing_precedent_id. Metrics correctly exclude gap
  edges (target not a precedent). No app.py change (gated via node_types).

Frontend:
- graph.ts: GraphNodeType += "gap"; node fields.
- graph-filter-panel: toggle "חוסרי מחקר (פסיקה חסרה)" (off by default).
- graph-canvas: gaps render as faint hollow dashed circles, never recoloured
  by color-by; sized by citation count.
- graph-node-panel: gap branch — "מצוטטת ע״י N פסיקות" + status badge + link
  to /missing-precedents.

web-ui build + lint pass. Invariants: G2 (SELECT-only), UI2 (model grows on
explicit Pydantic). api:types post-deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:21:53 +00:00
2fbc0cd3c2 feat(graph): centrality + cluster analytics (corpus graph PR B)
The Obsidian "Graph Analysis" equivalent — surfaces influence and structure
beyond raw citation count.

Backend (new web/graph_metrics.py — pure, dependency-free, no DB → G2):
- PageRank (power-iteration), betweenness (Brandes), community (deterministic
  label-propagation + connected-components fallback), computed in-memory over
  the precedent citation subgraph that build_corpus_graph already fetched.
  Normalized 0–1; community ints dense-ranked by size (stable colours).
- GraphNode += pagerank/betweenness/community (None unless metrics=true).
- build_corpus_graph + /api/graph/corpus gain metrics=false (default path
  unchanged). Validated on the live corpus: 147 nodes in 13ms.

Frontend:
- graph.ts: GraphNode metrics fields + metrics param.
- graph-canvas: color-by (type | practice_area | precedent_level | community |
  recency) and size-by (in-degree | pagerank | betweenness) via colorForNode /
  radiusForNode; exported palettes.
- graph-view: colorBy/sizeBy controls; metrics requested only when needed;
  global metrics overlaid onto neighborhood nodes by id (a node's PageRank
  shouldn't change when focused); a ranking panel (Tabs: המשפיעות / גשרים,
  click → focus); dynamic legend per color-by.
- graph-filter-panel: "צביעה לפי" + "גודל נקודה לפי" Selects.

web-ui build + lint pass. Invariants: G2 (metrics pure, no DB writes),
UI2 (model grows on explicit Pydantic). api:types post-deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 21:04:47 +00:00
8258f09228 feat(graph): metadata filters + facets (corpus graph PR A)
Adds legal-metadata filtering and the payload to color by it (foundation for
the color-by selector in the analytics PR).

Backend (web/graph_api.py, web/app.py) — read-only, G2:
- GraphNode += court, date (ISO) — precedents carry them for filter/color-by.
- build_corpus_graph += server-side WHERE filters (G5): court, precedent_level,
  chair, district, year_from, year_to (EXTRACT(YEAR FROM date)). Neighborhood
  query also selects court/date.
- New GET /api/graph/facets (response_model GraphFacets, UI2) → distinct
  courts/levels/chairs/districts so the UI doesn't hardcode Hebrew strings.

Frontend:
- graph.ts: GraphNode += court/date; GraphFilters += the six params;
  buildParams; useGraphFacets() hook.
- graph-filter-panel: an "advanced" Accordion with court/precedent_level/chair/
  district Selects (from facets) + year-from/year-to Selects.
- graph-view: new controls wired into filters; facets fetched and passed down.

Verified read-only against the live DB (precedent_level=עליון&year_from=2015
filters correctly; facets populated: 36 courts / 3 levels / 19 chairs / 4
districts). web-ui build + lint pass.

Invariants: G2 (SELECT-only via db.get_pool), G5 (filters server-side),
UI2 (explicit response_models). api:types to be regenerated post-deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:52:13 +00:00
9315ba4dfe Merge pull request 'feat(graph): in-app corpus citation graph (/graph) — Phase 1' (#113) from worktree-corpus-graph into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m44s
2026-06-07 18:52:01 +00:00
c80e4ce8ff feat(graph): in-app corpus citation graph (/graph) — Phase 1
Native, Obsidian-graph-view-like network of the precedent corpus, rendered
in web-ui from a read-only projection of the live DB. Replaces the idea of
exporting to an external Obsidian vault (which would be a parallel, drifting
copy of the corpus — the exact root cause G2 forbids).

The graph edges already existed in the data model; this only surfaces them:
nodes = precedents (case_law) + synthesized topic/practice-area hubs;
edges = cites (precedent_internal_citations) + same_chain (case_law_relations)
+ tagged/in_area (subject_tags / practice_area membership). Node size =
incoming-citation count (index-backed GROUP BY on idx_pic_target). Click a
node → local-graph neighborhood focus; panel deep-links to /precedents/[id].

Backend (read-only, SELECT only — G2):
- web/graph_api.py — Pydantic models (CorpusGraph/GraphNode/GraphEdge, so
  OpenAPI emits real types — UI2) + SQL assembly over the shared db.get_pool().
- web/app.py — GET /api/graph/corpus, GET /api/graph/node/{id}/neighborhood,
  both with explicit response_model. practice_area validated against the
  closed enum (G5); both endpoints write nothing.

Frontend:
- react-force-graph-2d (canvas/d3-force), loaded via next/dynamic ssr:false.
- /graph page + nav entry; graph.ts TanStack hooks; filter panel (practice_area
  / source / min-citations / search / node-type toggles), node detail panel,
  hover+selection neighborhood highlight. Explicit error handling (UI4).

Not a retrieval path (03-retrieval): returns graph topology, never ranked
search results. Halacha nodes + corroboration/equivalence edges are Phase 2,
already gated behind the node_types param (no contract change needed).

SQL validated read-only against the live DB (142 precedents, 85 resolved
citations, JSONB tag expansion, ANY(uuid[]) edge + BFS queries). web-ui lint
+ build pass; /graph in the route table.

Invariants: keeps G2 (single source of truth — live projection, no parallel
store), G5 (corpus separation filtered server-side), UI2 (response models),
UI4 (no swallowed UI errors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:50:56 +00:00
f3740fef68 Merge pull request 'fix(halacha): split authority (derived) from rule_role — stop source-conflation (INV-DM7)' (#112) from worktree-halacha-authority-split into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m32s
2026-06-07 18:19:43 +00:00