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>
This commit is contained in:
2026-06-20 16:04:04 +00:00
parent b8c49a1269
commit ccc5a73bc8
9 changed files with 193 additions and 4 deletions

View File

@@ -452,10 +452,19 @@ async def get_precedent(case_law_id: UUID | str) -> dict | None:
r["source_date"].isoformat()
if r.get("source_date") is not None else None
),
"treatment": r.get("treatment") or "",
"confidence": r.get("confidence"),
}
for r in raw
]
# Authority signal (X11 Phase 2, #154): how the citing committee decisions
# TREATED this ruling (followed/distinguished/…) — surfaced so the chair (and
# research agents via the tool output) can argue authority and avoid leaning on
# a repeatedly-distinguished precedent.
record["cited_by"] = (await db.citation_authority([case_law_id])).get(
str(case_law_id),
{"total": 0, "positive": 0, "negative": 0, "unclassified": 0, "by_treatment": {}},
)
return record