1299 Commits

Author SHA1 Message Date
07bd8bee48 Merge pull request 'feat(learning): graduated gate — panel-consensus style lessons auto-flow to writer (P0)' (#341) from worktree-graduated-gate 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-28 21:46:29 +00:00
044de2c0f0 feat(learning): graduated gate — panel-consensus style lessons auto-flow to writer (P0)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
The voice-learning panel produced 81 vetted style lessons — all stuck at
review_status='proposed' with 0 ever approved — so the writer (which reads only
'approved') received NONE of them. The system captured learning but never let it
flow. Chair decision (2026-06-28): a GRADUATED gate by content risk.

- STYLE lessons (categories style/structure/lexicon/tabular) the 2/2 panel kept →
  created as review_status='approved' → flow to the writer immediately, reversibly
  (chair vetoes in /training). The deepseek+gemini panel only emits style_method
  and only on 2/2 consensus, so this is exactly the gate's criterion; substance is
  already filtered out and skipped, and routes through the strict halacha gate.
- SUBSTANCE (halacha/precedent/fact) stays a HARD chair gate — unchanged.

style_lesson_panel.py: _review_status_for(category) sets the gate explicitly
(approved for _STYLE_CATEGORIES, else proposed); the apply loop passes it to
db.add_decision_lesson (which already accepts review_status — no DB change).

Spec: INV-LRN1 rewritten as the graduated gate (hard for substance; reversible
auto-flow for consensus style — still "under user control" via veto, per
NCSC/CEPEJ); §0.6 updated to match.

A one-time backfill of the 81 existing panel style lessons to 'approved' runs
separately (DB, post-merge).

Invariants: INV-LRN1 (amended — graduated), INV-LRN5 (style-only, substance never
auto-flows), G10 (human control preserved as reversible veto).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:45:56 +00:00
cfcfe4df48 Merge pull request 'fix(learning): כרטיס-אוצֵר כן-3-ערוצים + לכידת ממצאי-אוצֵר (source='curator')' (#339) from worktree-curator-learning-surface into main
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 10s
2026-06-28 21:28:53 +00:00
6427911baf Merge pull request 'fix(writer): always feed canonical anti-patterns to the writer' (#340) from worktree-writer-anti-patterns into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m25s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-28 21:24:57 +00:00
d7201736f2 fix(writer): always feed canonical anti-patterns to the writer
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
The learning loop measured anti-patterns but never corrected them. style_distance
detects markdown headers / bullet lists / mid-paragraph mini-lists (from the
canonical lessons.ANTI_PATTERNS), but the writer only received anti-pattern
guidance if a chair `anti_patterns` override existed in appeal_type_rules — and
none does. So _build_style_context's `if ov:` branch injected nothing, the writer
was never told to avoid them, and drafts kept emitting them (8137: 28 hits, the
worst, newest — anti-patterns were trending UP, not down).

Anti-patterns are structural invariants of Dafna's voice (continuous legal
narrative — no markdown, no bullets), not overridable preferences. So inject the
canonical ANTI_PATTERNS notes ALWAYS, from the same list style_distance measures
against (single source of truth), with any chair additions layered on top. This
closes the measure-but-don't-correct gap: the next draft should show the markdown/
bullet anti-patterns drop, and Path A (style_distance_history) will confirm it.

Invariants: G1 (correct at source — the writer, not a post-hoc stripper), G2
(one canonical anti-pattern list shared by detection and instruction — no parallel
list), INV-LRN4 (closes the feedback half of the draft↔final loop).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:24:24 +00:00
be774ab87e fix(learning): honest 3-channel curator card + capture curator findings (source='curator')
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 9s
תיקוני-מבנה ללולאת-הלמידה (TaskMaster #157):

1. כרטיס ה-curator (/training טאב "אוצֵר") ספר decision_lessons WHERE source='curator'
   — ערך שאף מסלול-קוד לא כתב → תמיד 0. עוצב-מחדש (דרך שער-העיצוב Claude Design, אושר)
   להציג ביושר את שלושת ערוצי-ההזנה לכותב: דיסטילציה→appeal_type_rules (180, זורם),
   פאנל→decision_lessons (81, ממתין), אוצֵר→source='curator'. get_curator_stats שוכתב.

2. drift ספ↔סוכן: 07-learning.md §1.1 + INV-LRN3 קבעו שהאוצֵר רושם ממצאים כ-decision_lesson
   source='curator', אך הסוכן כתב comments בלבד — הממצאים האיכותיים אבדו. נוסף כלי-MCP
   record_curator_findings + §A.5b ב-hermes-curator.md (read-only נשמר; הצעה מגודרת-שער).

3. get_recent_decision_lessons(limit=15) חתך בשקט — נוסף WARN על מה שנחתך (חוקה §6);
   הפתרון האמיתי = סינתזת-לקחים (TaskMaster #158).

Invariants: מקיים INV-LRN1/G10 (שער-יו"ר), INV-LRN3 (לכידה מובנית), INV-IA2/IA5 (מקור-אמת
יחיד), G12 (leak-guard עובר), G2 (אין מסלול מקביל — אותם מאגרים). פער-מימוש פתוח מתועד:
§A לא רץ אוטומטית על mark-final (pipeline-wake exits) → נדחה לתכנון נפרד.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:20:05 +00:00
c00e7b4f49 Merge pull request 'docs(spec): Path A — prospective held-out style-distance trend (07-learning §0.7)' (#338) from worktree-spec-path-a into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 8s
G12 Leak-Guard / leak-guard (push) Successful in 3s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-28 21:16:35 +00:00
927be5c6bb docs(spec): add Path A — prospective held-out style-distance trend (07-learning §0.7)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
Documents the prospective held-out methodology shipped in PR #337: why a clean
retrospective held-out is impossible (lessons stored universal/untagged → no
leave-one-out), and how the system instead captures a clean generalization
datapoint at final-upload — before this case's lessons are folded — via a
style_distance snapshot + lesson-pool size into style_distance_history,
surfaced by GET /api/learning/style-distance-history.

Adds §0.7, wires step [7] MEASUREMENT and INV-LRN4 to it. Notes the
change_percent style/content confound and the one-time clean window.

Invariants documented: INV-LRN4 (this is its trend surface), G2 (reuses
style_distance + appeal_type_rules — no parallel metric path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:16:11 +00:00
84afdcb36c Merge pull request 'feat(learning): prospective held-out style-distance trend (Path A)' (#337) from worktree-style-distance-history 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 10s
2026-06-28 21:03:50 +00:00
ab510328af feat(learning): prospective held-out style-distance trend (Path A)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 9s
A clean held-out test of voice learning was no longer runnable: every
final-uploaded case already has its lessons folded, and lessons are stored
universal/untagged so leave-one-out is impossible. Path A makes the test
prospective instead — capture the generalization datapoint at the one moment
it's clean.

On final upload, after the draft↔final pair is created but BEFORE this
case's lessons are folded (folding is a separate manual /training step), we
snapshot style_distance (anti_pattern_total, golden-ratio max-deviation,
change_percent) alongside the current voice-lesson pool size. Because the
draft was written with only the PRIOR pool, each row is a clean "with N
accumulated lessons, our draft on this unseen case scored X" datapoint. As
the pool grows over cases, a downward trend = learning generalizes.

- db: SCHEMA_V45 style_distance_history (append-only) + helpers
  voice_lesson_pool_sizes / record_style_distance_snapshot /
  get_style_distance_history.
- app: best-effort capture in api_upload_final_decision (never fails the
  upload); GET /api/learning/style-distance-history for the trend.

Reuses the existing style_distance service + appeal_type_rules pool — no
parallel metric path. The 8 existing cases are already folded, so the table
starts empty and fills from the next final (their clean window is past).

Invariants: G2 (reuse style_distance/appeal_type_rules — one path),
INV-LRN4 (measure the draft↔final gap; this is its trend surface). LLM-free
(style_distance is deterministic) so it runs in the container.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 21:03:10 +00:00
8d0e44937d Merge pull request 'fix(compose): wire staged-pipeline indicators to live learning-status' (#336) from worktree-compose-learning-indicators into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 44s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-28 17:55:02 +00:00
baf478d9dc fix(compose): wire staged-pipeline indicators to live learning-status
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
The two stage indicators in the compose page's "השלמה והעברה" rail
("הרץ למידת-קול" / "הרץ אימות-הלכות") were static placeholder text
translated from mockup 03 and never wired to data — they always read
"ממתין להעלאת הסופי", even after the final was uploaded and both
pipelines completed. (The real status was already shown correctly by
LearningStatusBadges on the case page.)

Wire them to useCaseLearningStatus (/api/cases/{n}/learning-status) — the
same source the drafts-panel badges use (G2). The trailing text is now
derived: "ממתין להעלאת הסופי" until the final is uploaded, then the live
state ("✓ הושלם · 12 לקחים הופקו · 12 הוצעו לאישור" / "✓ הושלם · חולצו 48
· 33 אושרו · 15 נדחו", or running/queued/failed).

Data-binding bugfix only — identical markup/classes (mockup-03 layout
preserved), so no visual redesign; within the design-gate bugfix exception
(chair-approved approach). tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 17:54:33 +00:00
6c487a2e85 Merge pull request 'fix(learning): extract precedent metadata inline on final-decision enroll' (#335) from worktree-enroll-inline-metadata into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 9s
2026-06-28 10:49:23 +00:00
8e2cfd602c fix(learning): extract precedent metadata inline on final-decision enroll
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 9s
When a chair's signed final decision is uploaded it is enrolled into the
precedent library, but _enroll_final_in_library only set deterministic
fields (citation, proceeding_type, date) and copied subject_tags from the
case's subject_categories — which is usually empty. The Gemini metadata
pass (subject_tags / summary / headnote / key_quote) was never triggered,
so the row sat at metadata_extraction_status='pending' with no subject
tags until a drain happened to pick it up. In practice a freshly-enrolled
final showed an empty "תגיות נושא" in the precedent edit UI.

Fix: after enrollment + citation, call the existing reextract_metadata
path inline (G2 — one path, full status lifecycle). It runs on Gemini
Flash over REST (GOOGLE_GEMINI_API_KEY, already in Coolify), so it is
container-safe — unlike the halacha path (claude CLI, host-only).
apply_to_record fills only empty fields, so the deterministic seeds and
any chair-curated subject_categories are preserved. Result surfaced in the
upload response (out["metadata"]); failures logged, upload still succeeds.

Also corrects two now-stale comments: the "Gemini returns no_metadata for
internal decisions" note in the enroll loop, and the "MCP-tool-only path"
docstring on reextract_metadata (true for halacha, not for Gemini REST).

Invariants: G1 (fill at source on enroll, not a read-time workaround),
G2 (reuse reextract_metadata — no parallel extraction path). LLM call is
Gemini REST, not claude_session, so the container LLM-call constraint holds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:48:46 +00:00
0bf787929c Merge pull request 'fix(renumber): update Paperclip issue linkage on case renumber' (#334) from worktree-renumber-paperclip-linkage into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 11s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-28 10:20:00 +00:00
8932bd1f54 fix(renumber): update Paperclip issue linkage on case renumber
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
renumber_cases.py step 6 only renamed the Paperclip project, leaving the
case↔issue linkage on the OLD case number. get_case_issues() keys on two
text surfaces the script never touched:
  • plugin_state.legal-case-number (value_json) — the authoritative linkage
  • issues.title — the '[ערר {cn}] …' tag the title-path lookup matches

Result: after a renumber the issues kept the old number, get_case_issues
returned [], and post-final actions (run-learning / run-halacha) silently
skipped with reason "no_issue" — surfaced in the UI as "לא הופעלה למידה".
Discovered on case 8137-11-24 (renamed from 8137-24).

Now the Paperclip step rewrites all three surfaces (projects.name +
plugin_state + issue titles); inspect_paperclip + the dry-run report show
the linkage-row and issue-title counts that will be rewritten.

The 11-case migration already ran; the matching DB rows were fixed
manually (incl. this commit's logic) for all stale cases. This change is
for any future renumber.

Invariants: G1 (normalize at source — the linkage, not a read-time
workaround). G12 note: this is a one-time host-side migration script that
already connects directly to the Paperclip DB by design (below the platform
port, like the existing projects/MinIO/Gitea surgery) — it does not add a
parallel runtime path through agent_platform_port.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 10:19:24 +00:00
4de555367d chore: fix .gitignore inline comments + add untracked code files
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m32s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 11s
Inline # comments in gitignore are not supported — they were silently
breaking three patterns (data/checkpoints/, data/adapter-migration-state.json,
.claude/agents/.generated/). Moved comments to their own lines and added
missing entries for runtime dirs (data/audit/, data/logs/, etc.) and
temp files (.interaction_tmp.json, .design-build/, .taskmaster bak files).

Also tracks previously untracked legitimate files: scripts, tests, docs,
skills references, .env.example, taskmaster templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 12:49:32 +00:00
b4a68cf5da feat(ocr): replace Google Vision with Mistral OCR as PDF fallback
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-27 10:15:13 +00:00
9ae7304d44 feat(ocr): replace Google Vision with Mistral OCR as PDF fallback
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
Switches the scanned-PDF fallback from Google Cloud Vision to
Mistral OCR (mistral-ocr-latest) for better Hebrew accuracy and
robustness against broken embedded OCR layers (e.g. case 1044-03-26
which returned English garbage through Vision).

Routing strategy (document-level, not per-page):
- PyMuPDF extracts all pages; pages that pass _text_quality_ok()
  use PyMuPDF output directly (free, ~50ms).
- If ANY page fails quality → Mistral OCR called once for the whole
  PDF, returning per-page Markdown for all pages (consistent format,
  no plain-text/Markdown mix within a document).

Markdown output preserved: Mistral returns ## headers and |tables|;
chunker updated to recognise ATX Markdown headers (##/###) as section
boundaries in _split_into_sections().

Config: GOOGLE_CLOUD_VISION_API_KEY → MISTRAL_API_KEY; allowlist
updated vision.googleapis.com → api.mistral.ai.
MISTRAL_API_KEY added to Coolify container env.

Invariants: G1 (single OCR fallback path, not parallel), G2 (no
duplicate extractor route), INV-AH (Mistral handles gershayim
natively; quote-fix only on PyMuPDF path).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 10:11:45 +00:00
f2a264a7da Merge pull request 'fix(status): add qa_passed to legacy display labels' (#332) from worktree-qa-passed-status into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 45s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 11s
2026-06-24 15:04:16 +00:00
e051fda0cb fix(status): add qa_passed to legacy labels + migrate 8174-12-24 to drafted
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
qa_passed was set by the old agent pipeline but not included in the
trimmed 10-status CASE_STATUSES list or LEGACY_STATUS_LABELS, causing
the status badge and workflow timeline to render nothing ("לא ידוע").

Added qa_passed → "טיוטה" to LEGACY_STATUS_LABELS as a display-only
fallback so any case still carrying this value renders correctly until
migrated. Case 8174-12-24 status updated to drafted via PUT /api/cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:04:00 +00:00
331b8c4249 Merge pull request 'feat(agents): reset button + fix comment routing fallback' (#331) from worktree-agent-reset into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 47s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-24 14:54:25 +00:00
82844a63c2 feat(agents): reset button + endpoint to clear stuck agent error state
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
Adds a one-click 'reset agents' action for cases where writer/QA agents
are stuck in Paperclip's error state (triggered by recovery loop or
failed run). Addresses the root cause documented in
reference_paperclip_recovery_loops: reassigns open issues from agents
back to the chair user, and calls reset_agent_session for each error
agent to clear wedged runtime sessions.

Changes:
- paperclip_client.py: reset_case_agents() — reassigns stuck issues +
  clears error status in Paperclip DB + calls reset_agent_session
- agent_platform_port.py: exports pc_reset_case_agents (G12 gate)
- app.py: POST /api/cases/{case_number}/agents/reset endpoint
- agents.ts: useResetCaseAgents mutation hook + AgentResetResult type
- agent-status-widget.tsx: 'אפס' button (shown only when error agents
  exist) with Dialog confirmation + loading state + toast feedback

Invariants: G12 (Paperclip only via port), G2 (no parallel path —
uses existing reset_agent_session + pc_get_case_issues).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 14:53:50 +00:00
a9d04c1e9f Merge pull request 'fix(arguments): validate claim_ids + per-row savepoint in argument_aggregator (#156)' (#330) from worktree-fix-argument-aggregator-savepoint into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m12s
G12 Leak-Guard / leak-guard (push) Successful in 3s
Lint — undefined names / undefined-names (push) Successful in 11s
2026-06-24 12:59:33 +00:00
c9d83431e0 fix(arguments): validate claim_ids + per-row savepoint in argument_aggregator
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
aggregate_claims_to_arguments crashed with "current transaction is aborted,
commands ignored until end of transaction block" on large cases (confirmed on
1027-04-26, 195 claims; reported via CMP-186).

Root cause: the proposition INSERT (legal_argument_propositions) was wrapped in
a broad except Exception with no savepoint. When the LLM echoes a
syntactically-valid-but-nonexistent claim_id, the FK violation
(legal_argument_propositions_claim_id_fkey) puts the asyncpg transaction into
aborted state. The except caught only that INSERT's error but never issued
ROLLBACK TO SAVEPOINT, so the next statement (the following argument's INSERT
into legal_arguments) raised InFailedSQLTransactionError uncaught and crashed
the whole call. With many claims the bad-UUID probability is high -> consistent
failure.

Fix:
- Validate each claim_id against the known set of claim ids fetched for the
  case before INSERT, so a hallucinated id never reaches the DB (G1: fix at
  source). Malformed UUIDs are already dropped in _normalize_argument; this
  catches the valid-but-nonexistent ones.
- Wrap the INSERT in a per-row savepoint (async with conn.transaction()) as
  defense-in-depth, so any unexpected constraint failure rolls back to the
  savepoint instead of poisoning the surrounding transaction.

Invariants: G1 (normalize at source, not symptom-catch on read). No parallel
path (G2). No silent swallow — skips are logged.

TaskMaster: legal-ai #156

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 12:58:03 +00:00
b57cd17408 Merge pull request 'fix(case-page): resolve full-page review findings (bugs + RTL + resilience)' (#329) from worktree-case-page-fixes into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 43s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 11s
2026-06-20 18:58:21 +00:00
2b591f5018 fix(case-page): resolve full-page review findings (bugs + RTL + resilience)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
Full code review of the case-detail page (14 components) surfaced these,
all fixed here:

Logic bugs
- case-edit-dialog: form.reset ran on the 5s useCase refetch while the dialog
  was open, clobbering in-progress edits. Now resets only on open→true.
- status-changer: `selected` never synced to async/external `currentStatus`
  (stale dropdown). Reworked to track currentStatus until an explicit pick;
  resets to tracking after save.
- decision-blocks-panel: `block.content`/`word_count` accessed without null
  guards (endpoint has no response model) → potential render crash. Coerced
  with `?? ""` / `?? 0`. `STATUS_LABELS[status]` now falls back to the raw
  status instead of rendering literal "undefined".
- document-type-editor: `await mutateAsync()` in async click handlers without
  try/catch → unhandled promise rejection. Wrapped (errors still surface via
  isError).

Resilience / hygiene
- page.tsx: a transient 5xx on the background poll flipped the WHOLE page to
  the error card and discarded loaded data. Now gated on `!data`, plus a
  "נסה שוב" retry.
- cases.ts useUpdateCase: invalidated casesKeys.all, which re-invalidated the
  detail it had just optimistically patched. Scoped to the list prefix.

RTL correctness (logical properties)
- agent-status-widget `mr-auto`→`ms-auto`; agent-activity-feed `mr-auto`→
  `me-auto`, icon `ml-1/ml-2`→`me-1/me-2`, required `*` `mr-1`→`ms-1`;
  document-type-editor list `pr-4`→`ps-4`.

Minors
- drafts-panel: `<a href>`→`next/link` (operations + citation links) for SPA
  nav. agent-activity-feed: issueMap memoized; comment Textarea aria-label.
  upload-sheet: `unknown` status no longer shown as green success (neutral
  icon + "רענן לאישור"). citations.ts: case_name typed `string | null`.

Design gate: visual-touching items (RTL gap side, retry button, neutral
upload icon) were chair-authorized via the reviewed-findings approval ("fix
all"); none alter an approved page layout — they are correctness fixes.
tsc clean; eslint clean (1 pre-existing form.watch warning, untouched line).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:57:43 +00:00
148b4b9bf6 Merge pull request 'feat(arguments): inline status banners + fix double-card nesting' (#328) from worktree-arguments-banners into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 43s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 11s
2026-06-20 18:36:21 +00:00
31029b2d43 feat(arguments): inline status banners + fix double-card nesting
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 3s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
Implements the Claude-Design-approved inline status banners for the
"חשב טיעונים" panel (mockup 25-legal-arguments-panel): the four endpoint
states (queued / exists / no_claims / skipped) now render as tone-coded
banners below the header instead of a transient toast. Toast is kept only
for hard transport errors.

Also fixes a pre-existing double-card bug found while reviewing the page:
the page's "arguments" tab already wraps the panel in <Card><CardContent>
(page.tsx:151-155), yet LegalArgumentsPanel rendered its OWN <Card> too —
unlike its sibling tab panels (DecisionBlocksPanel/DraftsPanel/
AgentActivityFeed) which render a plain <div>. The panel now matches that
convention (single card, no double border/padding), consistent with the
approved single-card mockup.

Design gate: card 25 approved by חיים. tsc + eslint clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:35:55 +00:00
c9970a5955 Merge pull request 'fix(operations): contain agent run-log text inside the "פלט" popup' (#327) from worktree-fix-runlog-overflow into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 43s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 11s
2026-06-20 18:32:17 +00:00
38234d9b4f fix(operations): contain agent run-log text inside the "פלט" popup
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
The RunLogDialog rendered the live agent log inside a Radix ScrollArea.
Radix wraps Viewport children in an inner `display:table` div that
shrink-wraps to the widest unbreakable token — the long `toolu_…` IDs and
`/home/chaim/…` file paths in the raw JSON log. That table cell expands
instead of constraining width, so `whitespace-pre-wrap break-words` never
had a width to wrap against and the text spilled out past the dialog
borders.

Replace the ScrollArea with a plain width-constrained scrollable div and
switch break-words → break-all so the long unbreakable IDs/paths wrap too.
Pure presentational fix (no invariant surface).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:31:17 +00:00
a0b158b2c8 Merge pull request 'fix(arguments): route "חשב טיעונים" through the legal-analyst agent' (#326) from worktree-fix-aggregate-btn into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 41s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-20 18:20:48 +00:00
a3df05e067 fix(arguments): route "חשב טיעונים" through the legal-analyst agent
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
The /aggregate-arguments endpoint ran an in-container BackgroundTask that
called claude_session (the local `claude` CLI) — which does not exist in the
FastAPI container. The button silently produced nothing, and on `force` it
destructively DELETEd existing arguments *before* the doomed LLM call.

Replace the inline task with the established delegation pattern used by
"חלץ עובדות שמאיות" (extract-appraiser-facts): a cheap in-container DB
pre-check (no_claims / exists), then a Paperclip wakeup of the company's
legal-analyst, which runs mcp__legal-ai__aggregate_claims_to_arguments
locally (where the CLI lives) and reports back. `force` now runs locally too,
so delete+recompute are atomic on the host — no more destructive failure.

Frontend: AggregateArgumentsResult becomes a discriminated union
(queued | no_claims | exists | skipped) and the toast is status-accurate
instead of the misleading fixed "refresh in a minute".

Invariants: G12 (Paperclip touch confined to paperclip_client behind the
agent_platform_port), G2 (replaces the broken path, no parallel capability),
engineering §6 (explicit statuses, no silent swallow).

UI change is logic/toast only (no visual-layout change) — within the
Claude-Design-gate bug-fix exemption. Richer inline status panels deferred
to the gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:19:57 +00:00
a3e612f22b Merge pull request 'feat(citation-verify): frontend "אימות פסיקה" tab (#154)' (#325) from worktree-citation-verify-frontend 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-20 18:19:28 +00:00
5108c854cf feat(citation-verify): frontend "אימות פסיקה" tab in the decision editor (#154)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
Frontend half of the citation-verification panel (backend in #323), as the agreed
third tab in /compose — matching the approved X17 mockup 24-citation-verification:

- lib/api/citation-verification.ts: hand-written types + useCitationVerification
  query + useVerifyCitation mutation (POST verify, invalidates the view).
- components/compose/citation-verification-panel.tsx: per legal argument →
  supporting corpus precedents with the cited_by authority chips (אומץ ×N /
  אובחן ×N), the exact supporting_quote, ✓ מאמת / ✗ לא רלוונטי verify actions, a
  per-citation chair-note field, and the per-issue 📡 radar (unlinked digests,
  pointer-only). Summary strip + INV-AH/INV-DIG1 reminders.
- compose page: third tab "אימות פסיקה" alongside עורך הבלוקים / עמדות וטענות.

No api:types regen needed (endpoints return assembled dicts; types hand-written
per the lib/api convention). tsc + eslint clean.

Invariants: INV-AH (writer cites only verified), INV-DIG1 (digest never cited),
G2 (consumes the one backend view). Visual matches the gate-approved mockup.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:19:03 +00:00
d7855f6284 Merge pull request 'fix(ui): case header — metadata parallel to title, parties clamped to 2 lines' (#324) from worktree-case-header-layout into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 43s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 11s
2026-06-20 18:11:55 +00:00
80809ca406 fix(ui): case header — keep metadata parallel to title, clamp parties to 2 lines
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
The hearing-date/updated/sync metadata dl dropped below the title (taking rows and
pushing the band down) when the appellants/respondents line was long, because the
outer row used flex-wrap. Fix:

- Drop flex-wrap on the title↔metadata row (and flex-1 on the title block) so the
  metadata dl stays parallel to the H1; the title block shrinks instead.
- Clamp the parties line (and the subject fallback) to 2 lines with line-clamp-2 +
  title tooltip, so a long party list no longer grows the band.

Chair-directed layout fix; existing components, no new design.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:11:32 +00:00
abe4c53df1 Merge pull request 'feat(citation-verify): backend for "אימות פסיקה" — per-argument support + verify gate (#154)' (#323) from worktree-citation-verify-backend into main
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 10s
2026-06-20 18:08:46 +00:00
5ede8a9653 feat(citation-verify): backend for the "אימות פסיקה" panel — per-argument support + verify gate (#154)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 12s
Backend half of the citation-verification tab (frontend follows in a separate PR):

- Schema V44: case_precedents gains argument_id (the legal argument it supports),
  case_law_id (the corpus ruling — for cited_by + dedup), verified (the INV-AH gate
  the writer respects) + verified_at. All nullable; chair_note already existed.
- db: create_case_precedent(+argument_id/case_law_id/verified),
  set_case_precedent_verified(id, verified, chair_note), list_case_precedents(+cols).
- services/case_citation_verification.build_view(case): per legal_argument →
  in-corpus suggestions (search_library per issue) each with the cited_by authority
  breakdown (db.citation_authority, X11) + merged attach/verify state + per-issue
  radar (case_digest_radar grouped by matched issue). Pure read/assembly; reuses the
  one corpus search + one authority query + one radar (G2).
- endpoints: GET /api/cases/{n}/citation-verification (the view) and
  POST .../verify (upsert verify/un-verify + chair note).

Validated on 1044-03-26: 14 arguments, all with support; 317/10 surfaces with
אומץ×11/אובחן×1; radar leads attributed per issue. Auto-approval untouched (chair
gate, INV-G10). Verify defaults to false — nothing is authoritative until the chair
marks it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 18:08:21 +00:00
33c10e4147 Merge pull request 'fix(ui): nav & layout — decision-editor in band, compose back-link, wider cases table' (#322) from worktree-case-nav-fixes into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 53s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-20 17:56:44 +00:00
81050181d7 fix(ui): nav & layout — decision-editor in band, compose back-link, wider cases table
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
Chair-directed navigation/layout fixes (existing components, no new design):

1. "פתח עורך החלטה" moved into the case-page band actions (right after "העלאת
   מסמכים") so it's reachable from EVERY tab, not only סקירה. Removed the now-
   redundant full-width CTA from the overview tab.
2. Prominent "→ חזרה לדף התיק" back-link added to the /compose header (the
   breadcrumb link was too subtle).
3. Home cases table: rail trimmed 360→280px and the title cell made min-w-0 so the
   table gets the width it needs and no longer shows a horizontal scrollbar.

No backend / API change. The larger NEW citation-verification panel stays gated
behind Claude Design (preview 24-citation-verification already pushed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 17:56:21 +00:00
00c8083cc9 Merge pull request 'feat(digests): calibrate case radar to distilled issues + per-issue attribution' (#321) from worktree-digest-radar-calibrate into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m27s
G12 Leak-Guard / leak-guard (push) Successful in 4s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-20 17:07:58 +00:00
21ff52aff9 feat(digests): calibrate case radar to the analyst's distilled issues + per-issue attribution
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 12s
The radar's query was built from dozens of raw claims (procedural-heavy noise), so
matches were thematic but imprecise and gave no reason WHY a lead is relevant. Now:

- Prefer the analyst's distilled legal_arguments (argument_title + legal_topic — one
  crisp CREAC issue per row) over raw claims.
- Search EACH issue separately and MERGE, so every lead is attributed to the case
  issue(s) it answers (`matched_issues`) — the chair sees "this ruling is for your
  'זכות עמידה' issue", not just a blended score.
- Fall back to the raw-claims blended query pre-aggregation; `source` reports the path.
- Shared `_radar_enrich` helper (gap status + action + matched_issues), bounded to 25
  issues to cap the per-issue fan-out.

Validated: 8124-09-24 (32 args → per-issue) surfaces betterment rulings each tagged to
its issue (היעדר השבחה / זהות הנישום / סעיף 7(ב)); 1044-03-26 (0 args) falls back to
claims unchanged. No tool/endpoint signature change (new fields pass through the dict).

Invariants: G2 (reuses the one digest search + arg accessor), INV-DIG1 (radar only).
No schema change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 17:07:15 +00:00
cc8fd0b853 Merge pull request 'feat(digests): case-contextual digest radar — surface unlinked digests as chair leads (X12)' (#320) from worktree-digest-radar into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m53s
G12 Leak-Guard / leak-guard (push) Successful in 7s
Lint — undefined names / undefined-names (push) Successful in 11s
2026-06-20 16:45:48 +00:00
70f93c3bd4 feat(digests): case-contextual digest radar — surface unlinked digests as chair leads (X12)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 5s
Lint — undefined names / undefined-names (pull_request) Successful in 14s
A digest pointing at a ruling we don't hold yet ("unlinked") was captured globally
(missing_precedents inbox) but never surfaced IN THE CONTEXT of the case being
decided — so a relevant ruling known only via a digest could fall through the cracks
at the moment it matters. Adds the case-contextual radar:

- db.search_digests_semantic: new `linked_only` filter (False = unlinked-only target set).
- digest_library.case_digest_radar(case_number): builds the case topic from title +
  appeal_subtype + the analyst's claims, embeds once, matches against UNLINKED digests,
  and returns leads enriched with the underlying ruling's gap status + suggested action
  (new_lead / gap_open / fetched / available_link).
- MCP tool `digest_radar` + endpoint GET /api/cases/{n}/digest-radar (for the agent and
  the future case-page lead).

INV-DIG1 preserved: radar only — every lead points at the underlying RULING (fetch /
upload / link), never cites the digest. Read-only.

Validated on 8124-09-24 (היטל השבחה): 5 on-topic unlinked digests, scores 0.64–0.72.
The visible case-page panel is a separate UI change → goes through the design gate.

Invariants: G2 (reuses the one digest semantic-search + gap/citation resolvers),
INV-DIG1 (no digest citation), INV-DIG3 (gap surfacing). No schema change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 16:45:14 +00:00
4f45fa416b Merge pull request 'feat(x11): treatment-aware citation authority wired into research agents (#154)' (#319) from worktree-x11-treatment-wiring into main
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 1m26s
G12 Leak-Guard / leak-guard (push) Successful in 5s
Lint — undefined names / undefined-names (push) Successful in 10s
2026-06-20 16:04:36 +00:00
ccc5a73bc8 feat(x11): treatment-aware citation authority wired into research agents (#154)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 10s
The internal citation graph fed only RANKING (raw in-degree), and the per-citation
TREATMENT was never classified — so a precedent distinguished N times got the same
authority boost as one followed N times (INV-COR2 violation), and the signal never
reached the agents' reasoning. Wires the full path:

Phase 1 — scripts/classify_citation_treatments.py: classify each linked edge's
  treatment (followed/distinguished/…) from its match_context via
  corroboration.classify_treatment (Opus 4.8 @ xhigh, local), filling
  precedent_internal_citations.treatment. Idempotent.
Phase 2 — db.refresh_verified_layer: count only NON-negative treatments toward
  verified/cite_count (INV-COR2/COR4). Unclassified counts as neutral-positive so
  the signal degrades gracefully before classification runs.
Phase 3 — db.citation_authority(ids): per-precedent {total, positive, negative,
  unclassified, by_treatment}. Surfaced as `cited_by` in search_precedent_library
  hits and precedent_library_get, and `treatment` per incoming citation.
Phase 4 — legal-researcher/analyst/writer prompts: weigh & ARGUE authority
  ("הלכה שאומצה ב-N החלטות ועדת-ערר"), flag distinguished/overruled, never invent
  the count (INV-AH; writer is read-only of the analyst).

Auto-approval stays kill-switched off (chair gate preserved, INV-G10). No schema
change (treatment column already existed). Operational: run the classifier +
refresh_verified_layer over the 379 edges, then sync agents across companies.

Invariants: G2 (one classifier + one authority query, reused), INV-COR2/COR3/COR4
(negative never corroborates; point-specific; ≥N), INV-G10 (no auto-approval),
INV-AH (no invented numbers).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 16:04:04 +00:00
b8c49a1269 Merge pull request 'fix(precedents): מראה-מקום never blank — seed at upload + inline Gemini enrichment' (#318) from worktree-citation-autofill-root 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 10s
2026-06-20 15:30:40 +00:00
b0bcdbeeef fix(precedents): מראה-מקום never blank — seed at upload + inline Gemini enrichment (root cause)
All checks were successful
G12 Leak-Guard / leak-guard (pull_request) Successful in 4s
Lint — undefined names / undefined-names (pull_request) Successful in 11s
Root cause: the upload form "מראה המקום" field is stored only as case_number;
citation_formatted was left empty and filled ONLY by the async metadata
extraction, which runs in the local drainer (not the container) because it was
lumped with halacha extraction. Result: the chair saw an empty מראה-מקום during
the window and re-typed it by hand.

Fix (hybrid — Option C):
- Seed: _create_external_record / create_external_case_law store the chair's typed
  citation as citation_formatted at INSERT, so the field is never blank. A
  cited_only→external promotion preserves an existing non-empty value (prior edit).
- Enrich: ingest_precedent runs metadata extraction INLINE in-container
  (gemini_session is direct REST, no local CLI) with force_citation=True, upgrading
  the seed to the canonical derived citation (parties + reporter + date) before the
  chair opens the page. Best-effort: on no key / API failure the seed remains and
  the queued metadata drain stays as the fallback. Halacha extraction is untouched
  (stays local — claude_session).
- apply_to_record / extract_and_apply gain force_citation: re-assemble even when
  citation_formatted is non-empty, writing only when assembly SUCCEEDS (seed
  preserved on abstention). The drainer keeps the default (False) so a chair's
  manual edit in /precedents/[id] is never clobbered.

Ops: GOOGLE_GEMINI_API_KEY added to the legal-ai Coolify app (runtime) — the SoT
value from Infisical nautilus:/external-apis/gemini, validated against the API.

Invariants: G2 (one citation-resolution + one metadata-extraction path, reused —
no parallel logic), INV-ID2/X1§3 (citation_formatted stays a derived field; the
seed is provisional and upgraded), INV-G10 (chair edits preserved). No schema change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 15:30:00 +00:00
2c515966c5 Merge pull request 'feat(precedents): surface auto-detected incoming citations in the "ציטוטים מקושרים" panel' (#317) from worktree-incoming-citations-panel 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 10s
2026-06-20 14:55:01 +00:00