eliziff gives legal-text detection an audit trail

This fork treats citation and legislative-text detection as something to prove against a corpus, not merely demonstrate in a happy-path demo.

discoverycompliance

eliziff has built a testing discipline around the unglamorous but consequential task of recognising legal material consistently. The work measures false matches, scanning cost and real sample failures before a pattern is accepted. Some early approaches were rejected or narrowed after those checks.

The toolkit covers several recurring document signals:

  • Citation anchors and statute structure, to identify the shape of legal references.
  • Canadian regulatory and statutory instruments, plus French legislative forms.
  • Amendment headings, to spot changes within legislation.
  • Shared treatment of Unicode, so typographic variation does not quietly distort results.
  • Deliberately corrupted test text, to test how recognition holds up on imperfect source material.

The useful part is the evidence trail: the reusable test cases and quality gates travel with any future adaptation, while the individual patterns remain tied to their jurisdictions and source collections.

So what Legal teams building discovery, knowledge or compliance workflows should care because dependable text recognition needs measured error rates before it can support review decisions.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

78 commits from eliziff/Beaver, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
3f0f8343 fix: drafting-error scans route to the deterministic lint again Eli Ziff 2026-07-27 ↗ GitHub
commit body
The new DOCUMENT EDITING action-request rules made 'check my DOCX for
drafting errors' read as an action request needing clarification, so
the model called ask_inputs instead of library_lint_docx_structure
(caught by the LIVE_E2E tool-loop suite, failing twice consistently).
One carve-out line restores the routing: error scans are review
requests; edit_document/ask_inputs only when the user wants fixes
applied. Verified live: suite green with a real model.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QUsMXzBsEJ27DjJVPSfW5o
e941fee6 Plan: three deterministic legal-text optimizations from LAB autopsy Eli Ziff 2026-07-28 ↗ GitHub
commit body
Anchor coverage diff (regex ledger + two-way omission/grounding report),
skeleton-first context (structural outline + section-scoped library_read),
and annotated tracked changes (reasons become anchored Word comments,
genre enforced by schema). Zero-LLM probe on pilot-b-03 already flags
4/13 judged misses; plan records designs, surfaces, and measurement.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
9367619e Add deterministic legal-text anchor coverage (lib, tool, retro harness) Eli Ziff 2026-07-28 ↗ GitHub
commit body
legalTextAnchors.ts extracts typed anchors - money (currency-tagged,
attached-suffix and range-aware), percentages, ratio multiples, full
dates, durations, US and Canadian statutory citations, neutral and US
reporter case citations - canonicalized to value keys so $2.25M equals
$2,250,000 and 3/15/2027 equals March 15, 2027. anchorCoverage() diffs
sources against drafts in both directions (omission candidates and
grounding candidates) and audits words-vs-numerals pairs. Exposed as
the bounded library_anchor_coverage tool; scripts/anchor-coverage-
retro.ts measures any source/draft file sets with zero model calls.

Retro vs the LAB pilot autopsies: pilot-b-03 flags 4 of 13 judged
misses outright (March 15 2027 maturities x2, $218.75M TLA balance,
5-Business-Day grace) plus the 3.68x/1.24x computed-figure cluster in
reverse; pilot-b-01 grounding catches the model-computed 47.1%
combined share and an out-of-corpus 16 C.F.R. § 801.10 cite, and the
omission side surfaces the never-cited statutory framework (15 U.S.C.
§ 18a(b)(1)(B), Clayton §7A, FTC Act §13(b)) and all four precedent
reporter cites. 24 unit tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
1af02f4b Anchor grammar: Canadian federal instruments (SOR/SI) Eli Ziff 2026-07-28 ↗ GitHub
commit body
Headline gap from the Canadian anchor-forms inventory (60 verified
forms, legal-generalization-corpus): SOR/2005-407-style citations
have no US analogue and fail silently in a US-shaped grammar. Both
year widths normalize to one key (SOR/83-593 -> stat:sor:1983-593).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
3a44942e Anchor grammar: recital-style ordinal dates and worded percentages Eli Ziff 2026-07-28 ↗ GitHub
commit body
First pass of the measure->fix->re-measure loop on held-out gold:
CUAD typed-containment recall (13,823 expert spans, 510 contracts,
zero LAB overlap) scored 39.0% overall with dates at 50.6%; the
dominant measured gap was recital drafting ("the 15th day of March,
2027", "March 15th, 2027") plus worded percentages ("fifty percent").
After the two grammar productions: dates 69.8% (+19.2pp), Agreement
Date 53.4%->76.3%, overall 43.9%. Duration categories were already
strong (Notice Period 82.0%, Renewal Term 73.8%). The money-category
ceiling is largely structural - many CUAD cap/commitment spans
contain prose with no extractable literal - and is left for the
corpus sweep rather than pattern-chasing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
6714f8d0 Hardcase stress harness: seeded corruption + misplacement curves Eli Ziff 2026-07-28 ↗ GitHub
commit body
structure-stress.ts measures what survives typos, OCR-class character
confusions, whitespace damage, and swapped 3-line blocks - seeded and
deterministic, scored self-relative against the clean parse. LAB
credit agreement: 92.4% label / 98.1% anchor recall at 0.5% per-char
corruption, 75.0%/85.6% at 2%, 78.9%/91.3% in the combined worst case,
with ladder restarts/violations rising as designed disorder signals.
CBCA statute run exposes two honest findings recorded for the sweep:
the statute plane has clean-parse ladder friction (552 violations
before any corruption), and @n occurrence suffixes cascade under
damage, so cross-version label recall needs base-label multiset
comparison rather than exact suffixed labels.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
081ad819 Add amendment calculus: prose-to-ops parser + verifying applier Eli Ziff 2026-07-28 ↗ GitHub
commit body
Legal text's native diff language, compiled both ways short of prose
generation: parseAmendmentInstructions turns US cut-and-bite and
Canadian replace-style amendment prose into typed ops addressed by
skeleton labels; applyAmendOps consolidates them deterministically and
doubles as the verifier (typed failures: target_not_found,
old_text_ambiguous, overlapping_ops - nothing silently mostly-applies).
Scoped heads (portion of/heading of) are refused, not guessed.

Measured zero-tuning against USLM gold (40 amendment-bearing public
laws, 4,349 typed amendingActions): quotedText recall 90.1%
(2,313/2,566), quotedContent onset recall 58.0%, 1,977 ops compiled,
195 refusals. Residual misses concentrate in one deeply nested tax act
and are refusals rather than misapplications; the 'substituting X for
Y' form was deliberately left out because block-interior prose would
compile false ops. Unit tests 15/15 cover both grammars, ambiguity
refusal, every-occurrence, period-at-end anchors, and scope isolation.

Prior-art vetting: legislative XML suites (Xcential LegisPro, Akoma
Ntoso change mgmt) are government-side and closed; recent generative
consolidation tops out at 61-63% correct (arXiv:2501.16794, LegiFrance)
and 50.3%/20.5% exact single/multi-step (NLLP 2025 German study) -
similarity metrics mislead, exactness is the product. Nobody ships a
contract-side deterministic applier, and none double as a rejection
sampler for small-model op translation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
d010821f Add deterministic deadline engine with derivation traces Eli Ziff 2026-07-28 ↗ GitHub
commit body
computeDeadline encodes computation-of-time law once: Interpretation
Act s. 27(2) exclude-first-include-last, s. 27(1) clear/at-least days,
s. 28 month anniversaries with month-end clamping, s. 26 holiday
rollover (direction-aware: before-deadlines roll earlier so the clear
period never shrinks), business-day counting, and contract-designated
extra non-business days. Holiday tables are computed from statutory
formulas (Easter computus, nth-weekday, Victoria Day Monday-before-
May-25) for CA federal / ON / BC / QC / US-with-observed-shifts, never
hardcoded per year.

Every result carries a derivation trace naming the rule and any holiday
hit - the rules-as-code lesson (Blawx/s(CASP)): for lawyers the
explanation is the product. durationNormToUnit bridges the anchor
extractor's dur:N:unit norms straight into engine parameters. 14/14
tests including jurisdiction deltas (BC has no Easter Monday, QC has
Saint-Jean-Baptiste, ON-only Family Day pre-2013-BC) and leap-day
anniversary clamps.

Vetting: rules-based deadline tools (LawToolBox, CalendarRules) are
commercial, US-litigation-court-rules products; nothing open,
contract-convention-aware, or Canadian-first exists. LLM weakness on
exactly this arithmetic is documented (Test of Time arXiv:2406.09170,
PRIMETIME, Date Fragments tokenization study).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
1ed2fda0 Add cross-document defined-term drift detector Eli Ziff 2026-07-28 ↗ GitHub
commit body
termDriftReport composes what we already parse - definition-list bodies
(straight + typographic quotes), skeleton section labels - across a
deal stack: same term with divergent bodies (first difference located
and excerpted), terms used in a document that defines them nowhere
(imported uses), in-document duplicate definitions counted not dropped.
Normalization is conservative (glyphs + whitespace only, never
wording), so every divergence shown is a real wording difference.

Vetting: Contract Companion / Definely / Lexis term checkers are
single-document, in-Word, commercial; LexNLP extracts definitions but
does no cross-document consistency. The deal-stack drift lane is empty.
5/5 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
c56b5e8c Add French anchor productions + bilingual concordance gate Eli Ziff 2026-07-28 ↗ GitHub
commit body
Canadian federal instruments are equally authentic in English and
French (equal authenticity rule); discordance between versions is the
raw material of shared-meaning-rule litigation and no tool checks for
it. The French productions normalize to the SAME keys as their English
twins, so concordance falls out of set intersection: dates (French
months incl. OCR-stripped accents, 1er ordinals), trailing-sign money
('2 250 000 $') and worded multipliers (millions/milliards de dollars,
d'euros), decimal-comma percentages (0,5 %), duration units (jours
ouvrables -> business_day, jours francs -> clear_day, mois kept whole),
statute series (L.R.C.->rsc, L.C.->sc, RLRQ/CQLR->cqlr, ch.->c,
art./par.->s), DORS/TR -> sor/si registers, and French neutral-cite
courts (CSC->scc, CAF->fca, CF/CFPI->fc, CCI->tcc).

bilingualConcordance(en, fr) wraps anchorCoverage into an
english_only/french_only/matched report with a discordance count.
English year form loosened to accept 'R.S.C., 1985' and '(1985)'.
35/35 anchor tests (9 new French + 2 concordance), no English
regressions; full lib suite green except one pre-existing
order-dependent flake in localAssistantTools (passes 8/8 in
isolation, unrelated ToA-bridge test).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
94cb6555 Read worded durations in English and French statutory drafting Eli Ziff 2026-07-28 ↗ GitHub
commit body
A2AJ probe of CBCA s. 133 (both language versions) showed federal
drafting writes durations in words - 'eighteen months' / 'dix-huit
mois' - leaving a digit-only grammar blind on both sides exactly where
the bilingual gate matters. English worded durations reuse the
existing number-word machinery; French gets a compositional table
(dix-huit 10+8, soixante-dix 60+10, vingt et un 20+1, quatre-vingt-dix
atomized to 80+10). Both normalize to the same dur:N:unit keys.
38/38 tests incl. a verbatim CBCA s. 133 EN/FR fixture that must stay
fully concordant (3 matched durations, 0 discordant).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
d0464f42 Wire amendment, deadline, term-drift, and bilingual tools into chat Eli Ziff 2026-07-28 ↗ GitHub
commit body
Four deterministic capabilities become library tools:
- library_apply_amendment: dry-run consolidation of an amending
  instrument against a source document - typed op receipts, refusals,
  hard failures, recompile verification; never writes a version
- library_deadline: traced deadline arithmetic (Interpretation Act
  ss. 26-28, business days, computed holiday tables CA/ON/BC/QC/US);
  schema tells the model to NEVER do calendar math in prose
- library_term_drift: deal-stack defined-term divergence report
- library_bilingual_concordance: EN/FR anchor concordance gate for
  equally-authentic bilingual instruments

All matter-allowlist-checked, all report-only. tsc clean; existing
localAssistantTools tests 8/8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
7f53fb31 Prove amendment calculus end-to-end on a real Canadian amending act Eli Ziff 2026-07-28 ↗ GitHub
commit body
Real-law probe: S.C. 2021, c. 11 (National Day for Truth and
Reconciliation) parsed from the Annual Statutes text and verified
against today's consolidations pulled from the LOCAL A2AJ bulk corpus
(AppData ALR Quote Verifier parquet plane - no network in the probe).
Result: 2/2 compiled replace ops' blocks present verbatim
(glyph-normalized) in current law (BEA 42(a)(i), CLC 193(2)); 2/2
scoped instructions refused with correct reasons (portion-of s. 35
chapeau, definition-in-s. 166); payload phrase in 4/4 targets.

Grammar fixes the real act forced:
- Canadian acts do NOT quote replacement blocks - unquotedBlock()
  captures the indented block after 'the following:', trimming
  marginal notes, next-instruction lines, chapter notes, and
  Title-Case heading furniture ('Coming into Force')
- definition-scoped heads ('The definition general holiday in section
  166...') now refuse like portion/heading heads instead of compiling
  a whole-section replace (misapplication risk)
17/17 unit tests; amend-consolidation-probe.ts is the reusable
harness (amending text + current-provision files + expected-phrase).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
3f1319a2 Add deterministic modal-force drafting lint + library tool Eli Ziff 2026-07-28 ↗ GitHub
commit body
legalDraftingLint: the citable, rule-like subset of drafting guidance
as exact-span findings with severity and autofix eligibility - 'may
not' prohibition ambiguity (federal legistic guidance: prohibitions
take 'must not'; no blind autofix, intent is residual semantics),
'and/or' (Law Smells ambiguous-syntax class), stacked-modal typos
(error), and mixed shall/must obligation registers (one finding at the
minority form, not per-hit spam), plus a document modal profile.
Detection is deterministic; a model only ever judges the bounded
excerpt - the deep-research report's lint-first/escalate-second
pattern, and its one idea today's suite had not already covered.

Wired as library_drafting_lint (allowlist-checked, capped findings).
5/5 unit tests; smoke over real corpus: Criminal Code 2.26M chars ->
shall 1723 / must 275 register drift + 58 may-not + 6 and-or; ON ESA
and the CX collective agreement profile as expected (CBAs are
and/or-dense, will-register). localAssistantTools tests 8/8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
53831b95 Fix stress metric: compare base-label multisets, not @n-suffixed labels Eli Ziff 2026-07-28 ↗ GitHub
commit body
Occurrence suffixes disambiguate restarts within one parse but cascade
under damage, making cross-version label recall non-monotonic (the
CBCA caveat from the original harness run). Comparing base-label
multisets restores a meaningful curve - ON ESA 2000: 95.9/90.7/85.2/
72.6/55.2% across 0.2-5% corruption, misplacement 97.1% salvage with
violations rising as the disorder signal.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
b27b3f79 Mask quoted runs before clause splitting; provision-first strikes Eli Ziff 2026-07-28 ↗ GitHub
commit body
The 58% quotedContent recall was clause splitting firing on ' by '
INSIDE quoted blocks, fragmenting them before capture. Splitting and
context scanning now run on a length-preserving masked body (quoted-run
interiors blanked, GPO paragraph seams honoured via
endOfTypographicRun); clause text always slices from the original.
Also: a provision ref directly after 'striking' now wins over quoted
text further along, so 'striking subsection (u) and inserting the
following:' compiles replace_provision instead of a bogus
substitute_text whose oldText was the whole block.

USLM re-measure: quotedContent onset recall 58.0% -> 90.2% (554/614),
quotedText 90.1% -> 91.9%, refusals 195 -> 137. Real-law probe
unchanged at 2/2 verified vs current consolidations. 18/18 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
6f38717a Compile French amendment heads (est remplacé par ce qui suit) Eli Ziff 2026-07-28 ↗ GitHub
commit body
French federal drafting joins the amendment calculus: FR head grammar
(remplacé/abrogé/modifié with gender-number agreement), French locator
compaction (42a)(i) → sec42(a)(i)), scoped-head refusals for
"Le passage de ..." and "La définition de ..." prefixes, and French block
furniture (Note marginale, L.R. chapter notes, Entrée en vigueur
heading) in unquotedBlock.

Measured: L.C. 2021, ch. 11 (fr) compiles 2 ops - both VERIFIED
verbatim against today's French consolidations from the local A2AJ
corpus (B-4 art. 42, L-2 art. 193) - and refuses the two scoped heads,
exactly mirroring the English probe on the same act. USLM English eval
unchanged (quotedText 2359/2566 = 91.9%, quotedContent 554/614 = 90.2%,
137 refusals). Probe normalize also folds corpus superscript ordinals
("1^er" → "1er"). Tests 20/20; anchors+skeleton suites green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
4c8fe69d Add French case to the qwen verifier-loop experiment Eli Ziff 2026-07-28 ↗ GitHub
commit body
Fourth fixture: the same CLC art. 166 definition amendment in French
(L.C. 2021, ch. 11, art. 4), chapeau and neighbour verbatim from the
local corpus's unofficial_sections_fr. Ideal ops score exact_match on
all four cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
9bccda00 Amend grammar: bare-token clauses at the end of a provision Eli Ziff 2026-07-28 ↗ GitHub
commit body
The audit over 8 amending acts (7,421 instructions EN+FR) ranked
add-provision as the largest unhandled mass (23.3%), and traced its
dominant refusal to the bare-conjunction-token clause: striking
out/adding a quoted token at the end of a paragraph. Worse, the
strike variant silently compiled UNSCOPED (whole-provision
strike_text with no end anchor) - a best-effort guess where the
calculus promises typed refusals.

- strike/substitute clauses parse the 'at the end of <provision>'
  tail: child-scoped target, last-occurrence anchor, whole-word match
- new append_text op: 'adding X at the end of <provision>'; the
  applier owns list re-punctuation (terminal '.' becomes '; X',
  terminal ';' gains ' X', anything else is a typed refusal)
- gold: SC 2021, c. 24, s. 1(1) round-trips onto today's CLC
  s. 164(1) consolidation exactly (normalized), 23/23 tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
0fbdbe41 Wire the SLA drafting workflow into /chat Eli Ziff 2026-07-28 ↗ GitHub
commit body
Spec→Ledger→Draft→Audit→Grounding, gated by MIKE_SLA_WORKFLOW=1:
the in-scope documents' skeleton outlines ride in the system prompt
with a section-scoped-reads contract (Ledger), the draft gets a
deterministic two-way anchor-coverage audit (amounts, dates,
sections, cites) with typed findings driving exactly one revision
pass (Audit), and both coverage reports persist as JSONL machine
receipts (Grounding, MIKE_SLA_RECEIPT_PATH). The LLM keeps
judgment; it loses bookkeeping. Benchmark runner passes the receipt
path beside the run artifacts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
456c3640 System prompt steers the deterministic organs Eli Ziff 2026-07-28 ↗ GitHub
commit body
The eight deterministic tools had zero prompt guidance - benchmark
receipts show models never call tools that are merely advertised.
The /chat prompt now directs outline-first section reads, a
pre-delivery anchor-coverage pass, and the deterministic organs
(term drift, drafting lint, bilingual concordance, amendment
apply, deadlines) over recomputing by hand.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
545c9806 Revise receipts always run the structural lint; count reasonless edits Eli Ziff 2026-07-28 ↗ GitHub
commit body
The determinism plan's flagship hook - deterministic same-turn
feedback on every library_revise_docx receipt - was gated behind the
opt-in annotate flag, so ordinary redlines got none. Lint runs
unconditionally now, and edits_without_reason is counted on every
revision so rationale coverage is a measurable variable (annotate
mode still forces it to zero by rejecting reason-free edits).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
dd208cf1 beaver_candidate arm becomes a real strategy: the SLA workflow Eli Ziff 2026-07-28 ↗ GitHub
commit body
The candidate arm mapped to the same context strategy string as
baseline - a comparison arm with nothing to compare. eval-run.ts now
spawns it with MIKE_SLA_WORKFLOW=1 (outline ledger, section-scoped
reads, deterministic audit + one revision pass) and the strategy
label records sla_workflow_v1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
59cf82e0 Align skeleton statute grammar with the corpus-proven reference Eli Ziff 2026-07-28 ↗ GitHub
commit body
The TS skeleton could not see the head style the A2AJ corpus actually
prints: dotless "164 (1)" (and NT/PE dot-terminated "2. (1)") heads were
invisible, so the skeleton/ledger machinery was blind to most local
statutes. Measured on a stratified 431-text sample across all 24 law
datasets: 11.6% label-level recall against the reference grammar
(verifier_core/a2aj_structure.py, used strictly as a read-only test-time
oracle), 185 of 296 structured texts fully unindexed.

statuteSpine.ts ports the reference design: bare-number marks join
strictly-increasing same-arity scopes, and only a scope with >= 3
members spanning >= 10% of the document becomes the section spine -
document-level competition instead of per-line guards, because a year, a
price, and a section head are indistinguishable line-locally. Two
corpus-measured departures from the oracle: dot-terminated heads compete
as a fallback family (indexes the NT/PE style the oracle misses;
precedence, not length, because Ontario-drafting paragraph lists are
dot-terminated and nested inside bare sections), and a lone dotless
provision excerpt is indexed via a bare-number-then-"(digit" guard
(single excerpts cannot form a spine).

After: 296/296 sampled structured texts exact-match, 100.0% label
recall (9847/9847), plus 71 NT/PE dotterm texts indexed beyond the
oracle. Probe + diff scripts included; amendops chapeau fixture flipped
to the true dotless consolidation form.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
18cba572 Retrieval gate: falsifiable vectors-only-if-earned benchmark Eli Ziff 2026-07-28 ↗ GitHub
commit body
Makes the vector-boundary rule executable: a versioned retrieval set
(48 items, 4 jurisdictions, gold locators + verbatim gold quotes), a
frozen corpus slice (48 docs / 2,162 sections pinned to the corpus
revision with parquet hashes), a deterministic scorer (Recall@5/10,
top-1 locator accuracy, unsupported-claim rate over claims), and
applyAsymmetricGate encoding the burden of proof: a candidate must gain
>= 5 points absolute Recall@10 with no regression elsewhere; ties fail.

Document identity is citationLookupKey, a Beaver-owned port of the
reference implementation's corpus-index normalizer, proven equivalent
by a differential test against an oracle dump (the probe imports the
read-only reference only at test time). The differential caught a real
divergence: Python casefold() folds sharp-s to "ss"; the port now does
so explicitly.

Baseline lexical-bm25-v1 measured: Recall@5 85.4%, Recall@10 89.6%,
locator accuracy 81.3%, unsupported-claim rate 0.0% (0/43), ~1 ms mean
per query. Numbers independently reproduced before commit. Set notes
carry the honest limitation: heading-derived queries are lexically
biased; adversarial set-v2 is required before the gate is
decision-grade.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
94e0a02a SLA audit reads the artifact deliverable, not just the chat text Eli Ziff 2026-07-28 ↗ GitHub
commit body
The smoke run exposed the gap: for a file-producing task the draft
audit read the chat message ("I've created the document...") and scored
0 matched / 59 source-only - then sent the model a repair prompt about
anchors that were sitting in the DOCX all along. The ledger now
snapshots the library (documentId -> current_version_id) at build;
collectSlaDeliverable diffs the library against that snapshot and folds
the text of documents created or revised during the turn into the
audited draft (deterministic auditor only - artifact text never enters
the model context; the model still sees only typed findings). Repair
instructions are artifact-aware: library-document deliverables are
revised via tools, not pasted into chat. Receipts record which
artifacts were audited.

Store-backed test proves the smoke shape (chat-only text: 0 matched)
and the fix (created artifact: 0 source-only). Field-name trap fixed en
route: listLocalLibrary returns the wire shape (current_version_id).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
dc862033 A failed edit anchor answers in the document's own words Eli Ziff 2026-07-28 ↗ GitHub
commit body
A missed anchor was the most expensive event in a drafting turn: the model
was told "Could not locate find=..., re-read the document" and could only
guess again at the same wording. Aider's harness measures this directly --
removing flexible hunk application and its "did you mean these actual
lines?" reports cost them a 9x increase in editing errors -- and our
matcher already had the flexible half (four-stage context cascade, smart
quote and whitespace normalization) without the teaching half.

applyTrackedEdits now diagnoses every miss against the document:

  ambiguous  -> the real context_before / context_after at each of up to
                three occurrence sites, verbatim, so the retry is a copy
                rather than a guess
  divergent  -> the longest matching prefix AND suffix, found by binary
                search over a seed-shortlisted set of paragraphs, quoted
                with the document's actual wording spanning the point the
                quote stops matching (this is what catches "5:00 pm" vs
                "5:00 p.m." and "The Buyer" vs "The Purchaser")
  applied    -> replacement text already present, find absent: typed as an
                edit that looks already applied rather than a mystery miss
  absent     -> names the parts tracked-change editing cannot reach
                (header, footer, footnote, text box)

The probes are bounded so diagnosis runs unconditionally: occurrence
counting stops at 20, the divergence probe seeds on the same 12 characters
that are already the reportability threshold and binary-searches at most 8
candidate paragraphs per side, so it costs a couple of passes over the
document rather than a similarity scan of every paragraph.

One matcher serves the production edit path, the local library path and
docxTextOps, so all three surfaces improve together. runEditDocument now
reports every failed edit's diagnosis instead of only the first, matching
how docxFieldValues already batches its complaints.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
1b09de4a SLA receipts name the anchors behind their totals Eli Ziff 2026-07-28 ↗ GitHub
commit body
The smoke run reported source_only=10 / draft_only=11, which is a number
you can only trust, not check. Reconstructing what those anchors actually
were took a snapshot of the run's data home and a throwaway replay script
-- and it was worth doing: three of the four money "omissions" turned out
to be quoted-printable fragments ($85,0 / $47,00 / $32) from the .eml the
lab arm wraps as .docx, not omissions at all, and one draft-only date was
simply the memo's own dateline.

The auditor already computes those rows for the repair prompt, so the
receipt now carries them too (capped per class at the same 12). A receipt
that names its anchors is falsifiable at read time, and every future run
documents itself without a replay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
b4ecb73f Prove the new tools are reachable, not just correct Eli Ziff 2026-07-28 ↗ GitHub
commit body
The post-wiring smoke ran sealed (MIKE_DISABLE_RESEARCH_TOOLS=1), so it
exercised the SLA loop but never touched caselaw_note_up,
library_compare_versions, or grep-mode find. Their unit tests prove the
executors work; nothing proved the dispatcher reaches them or that the
research gate puts them on the right side of the line.

Four checks, no model needed: the always-on document tools are advertised;
library_find exposes regex/case_insensitive and library_read exposes
offset/max_chars as real parameters; the citator disappears under the
research switch while document tools survive it; and dispatching
caselaw_note_up / library_compare_versions returns each executor's own
typed refusal rather than an unknown-tool reply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018bhP89cYSdEEjHNPnxacns
63cbbffb Area anchors; conflict scan reconciles arithmetic instead of keying subjects Eli Ziff 2026-07-29 ↗ GitHub
commit body
The subject-proximity scan measured 40 findings of pure noise and missed
the one real defect. Replaced with the two identities drafting actually
states: part/whole = percent and sum(parts) = total, checked at each
figure's stated precision, with typed abstentions for what cannot be
paired. Area joins the anchor classes (sqft/acre/sqm families, never
cross-normalized).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
3c04a76d Wire conflict scan as a tool; truncated reads say how to continue Eli Ziff 2026-07-29 ↗ GitHub
commit body
library_conflict_scan exposes the arithmetic reconciliation. The section
read and the windowed read previously set truncated:true silently; both
now return the offset that fetches the rest. Tool descriptions for the
coverage pair cut to what they do and return.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
5e7b4dfa SLA audit composes four organs, not one Eli Ziff 2026-07-29 ↗ GitHub
commit body
Audit was anchor coverage alone; it now also runs the conflict scan over
sources plus draft (draft-touching findings separated from source-vs-
source disagreements), term drift across the stack, and drafting lint on
the deliverable. Receipt keys are additive; lint warnings alone do not
spend the revision pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
16225b46 Temporal scan: date plus duration must equal the stated date Eli Ziff 2026-07-29 ↗ GitHub
commit body
Deadline arithmetic stated redundantly (a period after a named event,
with the resolved calendar date restated beside it) is checkable without
a model. Exact restatements check equality; within/no-later-than bounds
check the inequality. Business, clear, and trading days abstain typed
rather than approximate as calendar days.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
f0caf4e3 Held-out precision: 0.00 -> 1.00 conflict scan, 0.81 -> 0.96 term drift Eli Ziff 2026-07-29 ↗ GitHub
commit body
Measured on 147 held-out documents, every conflict-scan finding was
false. Three causes, three guards: a colon in the of-gap opens a labeled
field, not a part-of-whole pair; a percent must sit against the pair it
restates (reach 120 -> 40); a parts column with an unaccounted same-unit
figure before its total abstains as incomplete rather than adopting a
prose figure. Term drift stops comparing meaning-pointers as definition
bodies, skips strict-prefix truncation artifacts, and suppresses
imported-use rows where the document expressly incorporates another
instrument's definitions. The planted PSA defect still surfaces as the
sole finding.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
0e7ced23 Converged surface: section handles ride the native grammar Eli Ziff 2026-07-29 ↗ GitHub
commit body
Grep match lines end with the enclosing [section handle]; Read and Edit
accept section= (Edit requires uniqueness only within the section and
anchors on injected surrounding context); replace_all routes to the
deterministic text-ops engine. MIKE_PROMPT_VARIANT=lean serves the
condensed library block for the prompt-hygiene A/B.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
239cfce5 Temporal scan joins the SLA audit as its fifth organ Eli Ziff 2026-07-29 ↗ GitHub
commit body
Date-plus-duration identities that do not close now reach the repair
pass and the receipt, draft-first within the shared finding budget -
the module existed since morning but was wired into nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
96047132 Organ excerpts become addressable: at, verbatim, section on every ref Eli Ziff 2026-07-29 ↗ GitHub
commit body
Models mine quotes from organ excerpts - a thrifty strategy we design
for rather than against. Each anchor row and scanner ref now carries its
char offset, a verbatim flag that is true only when the display string
is the document's own bytes, and (scanners) the deepest enclosing
skeleton handle. Display strings are byte-identical to before; the PSA
baseline is unchanged with its finding now addressed to exhf.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
013aca10 Grammar tables: one authored pattern, two runtimes, proven per vector Eli Ziff 2026-07-29 ↗ GitHub
commit body
The cross-runtime contract that makes shared grammars safe: Python
compiles with re.ASCII and JS without the u flag, pinning digit, word,
and boundary classes to identical semantics; named groups are authored
JS-style and translated for Python; the validator bans what genuinely
diverges. Two pilot grammars ride the whole pipe with shared vectors;
the round-trip test executes both runtimes, and the validator caught
its first real authoring error before the first commit. Python loader
lives in the engine subrepo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
9007fcfc Grammar dialect widened to hold the battle-tested patterns verbatim Eli Ziff 2026-07-29 ↗ GitHub
commit body
The ALR/engine citation grammars use fixed-width lookbehind
((?<![\w.]), (?<=[.!?])) and rf-string composition throughout, so a
dialect that bans lookbehind cannot take them without semantic
rewrites - exactly what must not happen. Lookbehind is now allowed
and the dual-runtime vector check is the gate: Python re only
compiles fixed-width, and a test proves a variable-width table fails
the round-trip while JS alone would have taken it. Tables gain defs
({{name}} fragments, fixpoint splicing, cycle-guarded, mirrored
byte-for-byte in the Python loader). The non-ASCII \uXXXX ban is
dropped - its premise measured false (Python re reads \uXXXX in
patterns); only JS-only \u{...} stays banned.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
0e792666 Citation and pinpoint families extracted from the lineage, dialects intact Eli Ziff 2026-07-29 ↗ GitHub
commit body
citations.json reworked: the two hand-authored pilot entries are gone,
replaced by 16 entries extracted verbatim from the splitter lineage
(ALR == engine, md5-verified) and toa_maker where it diverges -
reporter, statute (toa's regulations branch), journal (the one-character
U+2019 split), legal titles, URL (+ the linking prefix dialect), book
frame, quoted and secondary citations. pinpoints.json carries both
pinpoint dialects on defs mirroring the source rf-composition; vectors
pin the real divergences: 'to' joins a list only in toa, subsection
only in splitter, 'at p.5' only in toa. Named groups and the FR->EN
court map are provenance-marked as ours. 73 vectors green in both
runtimes. Validator: escaped-paren false positive on the conditional
check fixed (journal grammars tripped it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
256efd78 Harvest 271 grammar vectors from the oracle test suites Eli Ziff 2026-07-29 ↗ GitHub
commit body
AST-extracted from ALR and TOA tests (read-only references), with the
tests' own assertions folded into structured expectations: 31 splitter
io vectors all carrying expected parts, 31 pure-ref prefilter vectors,
11 guard negatives, 36 toa io vectors with exact offsets, 162 raw
footnote strings. harvest.py regenerates when the references update.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
e7343070 Structure stress sweep: every corpus, oracles attached, measured first Eli Ziff 2026-07-29 ↗ GitHub
commit body
Inventory: A2AJ 225,017 cases + 23,531 laws (both with oracles: curated
cited-lists, JSON section labels + counts), 2,496 journal articles with
51,125 labeled pages, 3.5M CanLII titles; CourtListener is remote
(Supabase indexes + R2 bodies) and scoped accordingly. Harness streams
duckdb/sqlite rows to a worker pool (regex is the measured bottleneck,
1.6 MB/s/core), literal prefilter gates on the slow entries, and
short-string grammars are excluded from whole-document scans by policy.
Smoke baselines committed: journals page recovery 0.9997; laws section
recovery 0.811 with the weak sets named (SK/MB/NB rendering, pre-1970
consolidations); cases cite coverage 5094/5096; the naive paragraph
ladder learned to skip year brackets. Also: truncation test caught up
to the 949656c4 wording (my stale assertion, flagged by the
integration agent).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
99c9e300 Engine pinned at the table-driven splitter Eli Ziff 2026-07-29 ↗ GitHub
commit body
The engine's citation core now loads the grammar tables it was
extracted into: 36 hand-copied regexes across deterministic_citations
(26), core (7), docx_linking (3) and the adapters inline copy replaced
by lazy table handles with names kept, rf-string fragments deleted with
grep proof, tables vendored byte-stable with a live sync warning.
Gates: engine 39 tests, differential 0 drift / 0 unicode findings on
both legs, fork round-trip 16, docx_corpus behavior captures IDENTICAL
across 2,009 predictions in both modes. Also pinned: the ALR corporate
suffix guard port, the recall survey instrument (99.9% provider-gold),
and the portable-semantics loader. Bundle regenerated per procedure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
bcbc73f9 Sweep survives its own corpus: no full-tier sort, no giant-doc OOM Eli Ziff 2026-07-29 ↗ GitHub
commit body
The first full run died silently mid-transition from BCCA to BCSC:
ORDER BY at full tier forces duckdb to sort whole parquets (BCSC is
916 MB), and the pool deadlocked with the main process holding the
failures file open - visible only as a stale file handle hours later.
Full tier now streams unordered (sampled tiers keep deterministic
order), every court/set prints a progress line unbuffered, documents
over 8M chars scan head-only and record oversize_doc, and workers
recycle every 200 tasks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
40a75718 Engine pinned with the oracle-vector envelope Eli Ziff 2026-07-29 ↗ GitHub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
1279e219 Tribunal and judgment dialects join the tables, mined not invented Eli Ziff 2026-07-29 ↗ GitHub
commit body
cite.neutral.tribunal: a closed 13-member enum of the multi-token
court codes A2AJ providers actually attest (Comp Trib / Trib conc /
CIRB LD families - 1.87M citation cells, 26 MB of judgment prose, and
3.5M CanLII ids mined; every generic widening measured 6-9 false
positives, the enum 0). Closes the last recall gap: CT/fr 94.6 ->
100.0, Competition Tribunal now 552/552. cite.statute.judgment(.fr):
the anchors' own STATUTE_CANADIAN(_FR)_RE extracted verbatim - the
judgment dialect the footnote lineage scores 0% on, perfectly
complementary (89% of dotted/'c.', 76% of dotted/'ch.'), span-parity
0 mismatches over 4,350 documents. Residual 23.8% documented by cause
(chap., EN-series+ch., S.R.C. et al) - source-side questions, not
table patches. Agent's null-group vector expectations trimmed: they
pass Python's groupdict but fail JS (undefined !== null); both
runtimes green, 16 tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
24ee3149 Engine pinned with the dialect entries and 100% CT recall Eli Ziff 2026-07-29 ↗ GitHub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
eff4f79b Gate (2): measure the real scan path, price every entry, project the sweep Eli Ziff 2026-07-29 ↗ GitHub
commit body
perf_gate.py runs sweep.scan_doc itself over a seeded reservoir sample
(143 cases + 108 laws): 0.54 MB/s/core cases, 0.62 laws, cascade only
38 ms/MB. Projection from parquet counts: cases 330,518 doc-lang rows
~1.7 h at 4 BelowNormal workers (~1.1 h at 6), laws ~0.1 h. Top costs
are grammar entries, not structure: ref.note-reference 213 ms/MB,
cite.statute.toa 178, signal.aggressive 137 (signal vocab is
ubiquitous, so prefilter headroom there is limited).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
a099deaf sweep: run corpus tiers at BelowNormal priority Eli Ziff 2026-07-29 ↗ GitHub
commit body
Throttle doctrine baked in: main() drops its own priority class before
the pool spawns, so all workers inherit it. Proven by probe: forced
0x20 (Normal) -> guard -> 0x4000 (BelowNormal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
892bef44 Pin engine de5e180: column-order arbiter port (TFP port 1/5) Eli Ziff 2026-07-29 ↗ GitHub
commit body
661-gold inversion bounds: old 3.54% any feed; new 3.72% adversarial
raster / 0.75% trustworthy incumbent; two-column and margin subsets
improve at both bounds. Engine suite 49 passed + 34 subtests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
16f769be Pin engine 3687594: separator scan port (TFP port 2/5) Eli Ziff 2026-07-29 ↗ GitHub
commit body
Vector lane now reads rect-drawn rules (Word/LO exports); scanned
pages get the vendored raster scan, gated to OCR source (110 ms/page
measured, 0 cost on native). Engine suite 62 passed + 34 subtests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
667095b8 Pin engine: superscript splice port (TFP port 3/5) Eli Ziff 2026-07-29 ↗ GitHub
commit body
Flag-absent raised markers now attach as detached references; label
typographic proof extended to 0.75-0.8x with raise evidence. Engine
suite 70 passed + 34 subtests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
6f6d8bea Pin engine: note crossrefs port (TFP port 4/5) Eli Ziff 2026-07-29 ↗ GitHub
commit body
Deterministic supra/infra/op. cit. resolution over paired notes;
Footnote.crossrefs + NOTE_CROSSREF_UNRESOLVED witness; parser 0.2.0.
Engine suite 78 passed + 34 subtests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
113bd450 Pin engine: text-flow fault channels (TFP port 5/5) Eli Ziff 2026-07-29 ↗ GitHub
The five ranked TFP ports are all landed and measured. Engine suite
83 passed + 34 subtests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
8c183b29 sweep: worker-side parquet shards + derived prefilter gates Eli Ziff 2026-07-29 ↗ GitHub
commit body
The first full run starved: a single-threaded parent fed whole
documents from duckdb through IPC and 4 workers idled at a 4.6% duty
cycle (~26 h trajectory vs the 1.7 h projection - the perf gate had
measured the scorer, never the feeder). Full tier now ships tiny
(parquet, set, lang, row-slice) jobs; workers decode their own rows
with pyarrow (measured 490 MB/s on FC) and return per-shard aggregates,
so the parent never holds document text. Sampled tiers keep the
parent-fed path; both paths share _fold_record/_summarize so they
cannot drift. Proven: probes/shard_gate_check.py reproduces the legacy
aggregation EXACTLY on CMAC and CHRT (docs, chars, every counter).

Prefilter gates are now also derived from the pattern AST where no hand
gate exists (mandatory-literal OR-sets - sound by construction; 3 new
entries gated, 1.25x measured scan speedup), and the probe asserts zero
gate misses over a 1,862-doc reservoir sample. That assertion caught a
real pre-existing bug: cite.statute.judgment.fr's hand gate listed only
dotted heads (l.r/l.c) and silently undercounted dotless LRC/LC/CQLR
citations in every earlier run; the gate now carries the dotless heads.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
742d940b sweep: duckdb slice reads + anchor-windowed scanning (probe-proven) Eli Ziff 2026-07-29 ↗ GitHub
commit body
Feeder fix: scan_shard reads its own LIMIT/OFFSET slice via a worker-local
duckdb connection (threads=1) - pyarrow iter_batches held each single-row-
group column chunk (~2.5 GB BCSC) per worker and OOMed at 6 workers.

Scan fix: anchor-windowed match counting. Every entry gets a mandatory-
literal OR-set (AST-derived where possible, else hand PREFILTERS/_HAND_
ANCHORS) plus an AST max-width pad (bounded repeats exact; unbounded
assumed <=64-span, capped). Matches only ever live inside merged windows
around anchor hits; real-coverage bailout (>60% doc) and an edge clip-
guard fall back to full scans. shard_gate_check now asserts windowed==full
per entry per doc, zero tolerance: 0 gate misses, 0 window mismatches over
1862 docs x 39 entries; 1.98x reservoir scan speedup; CMAC+CHRT sharded
aggregation byte-identical to straight reads (SLICE_ROWS=1000 forced
multi-slice). The differential caught two real bugs en route: a bounded-
repeat width cap clipping signal.source tails (133 mismatches) and a hand
anchor set missing signal.source inline branch.

Whole scan_doc: 0.68 MB/s/core on the 172-doc spread sample -> ~35-40 min
projected for the 9.6 GB full tier at 6-7 workers (vs ~26 h observed
trajectory of the parent-fed run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
c35d45e3 docs: provider structural-richness survey (local data + code survey) Eli Ziff 2026-07-29 ↗ GitHub
commit body
Answers the standing question of what structure each provider already
hands us as data vs what Beaver consumes. Headlines: A2AJ citation graph
(cases_cited/citing) stored locally but dropped at ingest and rebuilt by
regex; CAP star-pagination + footnote asides compile to zero blocks while
compactOpinion prefers the rendition it cannot read; TNA level TOC and
<ref> authorities silently dropped; GOV.UK ET paragraph HTML tag-stripped
before compile; laws lane correctly native via unofficial_sections; A2AJ
cases confirmed structure-free (regex spine justified); journals re-derive
page boundaries the page_map states as data.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
ae68a969 Pin engine 2821387: anchored_scan grammar accelerator Eli Ziff 2026-07-29 ↗ GitHub
commit body
Anchor-windowed scanning as a permanent engine capability (AnchoredPattern
next to compile_entry): 1.98x proven on the 1,862-doc reservoir with 0
span mismatches; every grammar entry differentially re-proven in the
engine suite (91 + 34 subtests). Same machinery now drives the running
full sweep (7 workers saturated at 6.2 cores, 2.1 GB flat).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
7ce2636f full sweep before/after: ledger row 11 + rerun summaries Eli Ziff 2026-07-30 ↗ GitHub
commit body
Rerun over the whole local A2AJ corpus with the vet-driven fixes
(dbb7b355 structure, 6ae6d330 laws) landed:

  cases  fail 14.13% -> 10.35%; none 10.74% -> 8.46% (7,530 docs
         converted to paragraphs); narrow flags 3.38% -> 1.88%;
         endnotes/pages shares unchanged; 2.37 MB/s (fixes cost no
         throughput)
  laws   fail 14.0% -> 1.51%; recovery_mean 0.8254 -> 0.9613
         (n=34,107, single_instrument pseudo-labels excluded);
         NT/PE sets 0.03-0.11 -> 0.985-0.996
  journals  circular page scoring gone; provider_metadata kind,
         grammar-entry rates only, 0 fail

results/full summaries committed (summaries only, per the smoke
convention). Ledger row 11 carries the full table and commit trail.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
73284884 sweep: import the engine's anchored_scan; pin engine consumers commit Eli Ziff 2026-07-30 ↗ GitHub
commit body
The anchor-windowed scanning machinery sweep.py carried privately
(~270 lines: gate/anchor derivation, window merge, clip-guard) was
extracted into the engine as legalpdf/anchored_scan.py (engine 2821387).
Now that the full rerun is off the machine, the sweep drops its copy and
consumes the engine module: _ENTRIES becomes (id, AnchoredPattern,
gate), scan_doc counts via pattern.finditer(text, lower), and the
engine handle owns every fallback (small text, .lower() length drift,
coverage bailout, window-edge clip). Kept sweep-side: the _HAND_ANCHORS
per-match-mandatory sets, PREFILTER-gate precedence, and the launch
discipline. probes/shard_gate_check.py and probes/perf_gate.py follow
the new entry shape; perf_gate's per-entry cost now times the real
production path (windowed), not the bare full scan.

Equivalence notes: sweep's _ANCHOR_MIN_LEN=2 was dead weight (literal
runs flush at >= 3 chars, so no 2-char literal ever reached the filter)
- the engine's stricter constant derives identical anchors. The empty
_NO_ANCHOR opt-out set died with the copy.

Gates: shard_gate_check reservoir differential re-run green - 0 gate
misses, 0 window mismatches over the sample, shard path reproduces
legacy aggregation exactly. Engine pin 9e72346 ("Consumers adopt
AnchoredPattern for whole-input scans"): deterministic_citations
full-doc scan sites + benchmark._CITATION_RES go windowed, measured
1.57x on 21 real SCC decisions with byte-identical spans; engine suite
93 passed + 34 subtests.

Integration plan item 6 (task #28). TS mirror + derived-anchor drift
gate stay deferred to Phase 4 (engine anchored_scan.py docstring carries
the pointer).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
ed8f6f0c sweep hygiene: reservoir-sampled failures file; CHRT/CITT stub sentinels Eli Ziff 2026-07-30 ↗ GitHub
commit body
Two instrument defects from the full-run vet (task #28 item 7):

1. The failures file was a head-cap: the first 2,000 failing docs in
   scan order, so the vet queue only ever showed the alphabetically
   first courts and the corpus tail never appeared. Both pool shapes now
   reservoir-sample (Vitter's R, seed 47) inside _fold_record, and the
   sharded merge allocates the 2,000 slots across shard reservoirs
   proportionally to each shard's failure count (largest remainders fill
   the slack) - a uniform sample over ALL failures; the old merge kept
   whatever the first shards to finish had head-capped.

2. CHRT/CITT provider catalog cards whose Decision Content section
   states "There is no document available for this decision." / "Il n'y
   a pas de document disponible pour cette décision." (en 185 + fr 186
   docs, probed 2026-07-30) were counted as no_addressable_structure
   failures. They now classify as structure kind stub_sentinel - a
   provider inventory fact, not a detector miss - matched only in the
   document tail where the provider layout puts the sentinel, so a
   decision QUOTING the phrase stays a real document.

Verification: smoke-tier cases sweep green (5,096 docs, 77.8s; the
1,331-row failures file now spans 25 distinct courts). Targeted scan of
20 real sentinel docs across CHRT/CITT en+fr: 20/20 classify
stub_sentinel with zero fail flags; a long real CHRT decision and the
same decision with the sentinel phrase spliced MID-text both stay
"paragraphs" (the tail anchor holds). The shard-vs-legacy equivalence
gate is untouched by the sampling change - both pool shapes fold
through the same _fold_record and the compared aggregates carry no
failure rows.

Left open from item 7, deliberately: the competition-rule redesign for
the three true mis-selection classes (length-beats-style hijack,
gap-splice chimera, collapsed-fragment lock). That is a measured
detector change gated on the narrow-flag queue as its eval set - the
same discipline as dbb7b355 - not an instrument fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
91b58342 grounding: source-anchored re-test PASSES on RegLab expert labels Eli Ziff 2026-07-30 ↗ GitHub
commit body
CourtListener citation-lookup went 401 (auth now required), so
resolution/fetch moved to CAP static files (anonymous CDN): 289/442
citations resolved, 294 casebodies. Scoring each claim against its own
cited cases with the shipped lintLegalClaim: max-pooled
novel_content_fraction AUC 0.829 grounded vs misgrounded on expert
labels (n=8 positives - smoke pass, not calibration). Claim
segmentation alone un-inverted the alienness signal; the earlier
negative result was unit mismatch first, reference mismatch second.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
39bf94aa Journal footnote pairing: TFP-essence adapter over the plaintext export Eli Ziff 2026-07-30 ↗ GitHub
commit body
Proposition<->authority pairs from public_endpoint.db, the last H12
candidate source. Ports the essential logic only - engine best_chain
backbone DP, first-occurrence monotone ref assignment, _sentence_at -
over our own rendered plaintext (page map walked as data, N<TAB> and
N<spaces> label dialects), no kraken/ppdoc/codex stack. Case citations
via the citator anchor grammar, keyed for the citator join; date-shaped
reporter noise filtered with counters. Grammar-table vectors run at
startup as the loader's proof. Output: citator/journal_commentary.sqlite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
4e983cfc Stage 7/8 pre-registrations; third journal label dialect (N.<TAB>) Eli Ziff 2026-07-30 ↗ GitHub
commit body
Stage 7 freezes the soft lint-gated cascade (H7/H13/H14 features as a
revision-only pre-checker gate); Stage 8 freezes attested framing over
the widened tier with Charlotin Misrepresented probes and HousingQA as
no-op control. Dialect fix: Osgoode/Dalhousie-style `N.<TAB>` labels
were invisible (8,754 long articles zero-label); the backbone DP
out-competes the section headings that share the shape - article 214
goes 0 -> 115 selected labels with 0 unresolved crossrefs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
c99d9fb1 docs: record upstream-pairs survey; bare-dialect experiment reverted Eli Ziff 2026-07-30 ↗ GitHub
commit body
All 6,937 registered final-contract packages resolve locally with
pages.jsonl fn_label/fn_ref annotations (footnote_pairing_v2 output);
plaintext bare-N dialect attempt reverted (page-furniture collision,
237k->178k paired regression). Upstream lane supersedes further
plaintext dialect work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
085ad298 R5 amendment: lint is an ablation arm (raw vs linted sweep), registered pre-measurement Eli Ziff 2026-07-30 ↗ GitHub
commit body
Labeled context-arms runs with per-label receipts and sidecars; frozen
prediction and retirement rule for the lint arm; 429/backoff ops note.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AgUm5EcRKbT3duVFMbrQD
e37ca5f3 Stage 18 instrument fix: committed pool re-scorer + --output on the ctx sweep Eli Ziff 2026-07-31 ↗ GitHub
commit body
legalbench_pool_rescore.py corrects a raw-coordinate pool sidecar at SCORE
time (raw -> LF offset mapping + union-merge + clip), for receipts that
cannot be re-run for free. It prints the three readings separately so the
coordinate defect and the double-count defect stay attributable.

On stage18-fused-pools.jsonl it reproduces the audit's corrected figures
exactly: maud pool R@48 0.6795 recorded -> 0.9205 lf-mapped -> 0.8625
corrected; ALL 0.9584. Independent code path, same numbers.

--output lets a sweep write receipts under an explicit name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9ToHYJVDxfeJcJwdzrP2H
d6813dd7 Receipts refuse to clobber: --output honored in every sweep mode Eli Ziff 2026-07-31 ↗ GitHub
commit body
The --stage18 sweep hardcoded its receipt path, so a re-run silently
overwrote stage18-retrieval-arms.jsonl and the sha pinned in the
experiment log stopped resolving. Both sweep modes now route through
receiptPath(), which honors --output and throws on an existing file
unless --force is passed. Receipts are append-only evidence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9ToHYJVDxfeJcJwdzrP2H
1e810f46 Arm hygiene: stop arm A advertising arm B, and state the grammar once Eli Ziff 2026-07-31 ↗ GitHub
commit body
Two defects in the arms, found by tokenizing them instead of eyeballing.

ARM A WAS ADVERTISING ARM B. `forNavShape` stripped parameters but shared
descriptions, so legacy's library_outline told the model to use
"library_read at=" -- a parameter legacy does not have. An arm that
instructs the model to call something it cannot call measures the harness,
not the surface. Legacy now carries its own outline description.

`at` MEANT TWO THINGS. It is a character offset on a library_find hit and an
ADDRESS on library_read, so in the address arm at="12345" parses as a
structural locator and fails. The address arm now returns at="off:12345"
alongside a numeric `offset`, so a hit is directly passable to the tool it is
meant to feed.

DEDUPLICATION, which I had promised and not done. The page-scheme
explanation was written out three times; it now lives once, in
library_outline -- the orientation call, which is where a model looks before
it needs an address. read.at and find.at are pointers.

  nav cluster  1,327 -> 1,245 tokens in the address arm (-82)
  whole schema 10,939 -> 10,857

Honest accounting: the address arm is still +545 tokens over legacy, and no
amount of prose-trimming closes that, because 206 of it is library_links --
a tool legacy does not have -- and the rest is `from`, `follow` and `depth`,
which are capability rather than wording. "Compact" was the wrong promise for
a surface that adds a tool; the achievable claim is that nothing is said
twice, and that is now true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9ToHYJVDxfeJcJwdzrP2H
595e2333 Amend v2 registration: Quebec withdrawn; bilingual moves to federal EN/FR Eli Ziff 2026-07-31 ↗ GitHub
commit body
Step 2 established the licence position for every candidate fixture source
before any document was cut, and one result withdraws a family registered
hours earlier.

LegisQuebec requires prior authorisation from Publications du Quebec, with
royalties, binding on for-profit and non-profit bodies alike - and names this
use case directly: anyone downloading its content to process with, or to
feed, an AI tool must obtain authorisation first. Only hyperlinking is free.
So Quebec material is not merely non-redistributable, it may not be used as
unshipped processing input either. The Bilingual and Quebec family is
withdrawn; no Quebec text enters the benchmark and none is read into a model.
A2AJ excludes Quebec and Nunavut entirely, which corroborates the reading.

Bilingual survives on a cleaner footing: Canadian federal enactments are made
in both languages with both equally authoritative, and SI/97-5 permits anyone
to reproduce them without charge or permission. A2AJ carries the French
mirror of every federal row.

Also frozen before any fixture is cut: CUAD and MAUD redistributable under
CC BY 4.0 (with the caveat that neither GitHub repo carries a LICENSE file,
so the grant rests on the dataset card and Zenodo record); SEC EDGAR exhibits
redistributable in practice but NOT by grant, since 17 USC 105 does not reach
a privately drafted agreement - stated as risk, not licence; Ontario statutes
and regulations under the King's Printer policy; US federal AO forms under
105. Excluded: ContractNLI's scraped corporate NDA templates, PrivacyQA,
PSPC SACC, Ontario court forms, US state court forms, and A2AJ's BC, PEI and
Saskatchewan laws.

Records one repo-hygiene finding outside this benchmark: the bundled
CourtListener data is licence-clean but carries no attribution anywhere in
the repo, and there is no THIRD_PARTY_NOTICES.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9ToHYJVDxfeJcJwdzrP2H
d955b15f Retract amendment 2026-07-31c; reinstate the bilingual family Eli Ziff 2026-07-31 ↗ GitHub
commit body
The previous amendment gated fixture selection on a licensing analysis this
benchmark had no business making. Scope and permissions for this work are
settled outside it. Retracted in full: the withdrawal of the bilingual and
Quebec family, every redistributable/reference-only/do-not-use verdict, and
the CourtListener attribution observations, which were not findings of this
experiment.

Fixture provenance is now a reproducibility record - source, citation,
modifications, obscurity, retrieval date - not a legal one. licence and
licence_verdict are gone from the type, and nothing is gated on them.

Quebec is restated as what it is: an availability fact. The A2AJ coverage
tool reports 24 legislation and regulation datasets and none of them is
Quebec, so Quebec statutory material is not obtainable from the corpus this
benchmark draws on. The family does not depend on one - a bilingual pair
where an edit to one version and not the other is a defect works on any
equally-authoritative instrument.

Also records what coverage actually reports rather than what spelunking
guessed: 962 federal statutes and 4,876 federal regulations, bilingual
throughout, and fetch_document takes output_language=both, which returns the
English and French versions together - the bilingual fixture with no
assembly. The corpus is queried through coverage, search_legal_documents and
fetch_document, not by reverse-engineering the on-disk layout.

Carries forward the one real method note: assert text[start:end] == expected
on the scorer's own bytes before quoting any v2 number.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H9ToHYJVDxfeJcJwdzrP2H
a14c3b9e bench: focus deterministic review on attributed anchors Eli Ziff 2026-08-03 ↗ GitHub
c89913e5 @ feat: wire H1/H2/H3 deterministic omission organs, outline arm, conformance suite, Tier-0 drafting slicer Eli Ziff 2026-08-03 ↗ GitHub
commit body
H1 derivedValueScan - percent-of-base carry-through omission (analytical-only
gate, base-aware engagement, totality refusal, 11 tests)

H2 deadlineOmissionScan - "date ± duration" → resolved-date omission
(analytical-only gate, RefusalKey excludes scan_capped, 15 tests)

H3 undefinedTermScan - capitalized defined-term-style phrases the draft uses
but no source defines (works under both analytical and operative; quoting/use
boundary + caption detector for markup-analysis exemption, 17 tests + probe)

All three wired into auditSlaDraft (7 organs now), repair prompt +
worthARevision gated, 52 tests + tsc clean.

Outline arm grounded_structure_outline_v1 - one-time bounded outline +
cross-ref injection block, additive, preregistered (MIKE_GROUNDED_OUTLINE_INJECTION env gate).

Markdown↔docx capability-conformance suite (16 tests) + matrix doc covering
tables, auto-numbering, tracked changes, headers/footers, footnotes, text boxes.

Tier 0 drafting-phase token slicer - 81 runs enumerated, 44 sliceable,
per-arm aggregate over grounded-cache v1/v2/v3 batches.

Generalization probes + operationalization doc (O0-O5).

Co-Authored-By: Claude <noreply@anthropic.com>
@
96b39f35 docs: potential upstream mike PRs (parser coercion, find quote grammar) Eli Ziff 2026-08-05 ↗ GitHub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pfUVhNFTRvhYGXBwoKNj6
061289f1 feat(lab): anchor SECT-INDEX entries by bare numeral + heading confirmation Eli Ziff 2026-08-06 ↗ GitHub
commit body
Documents that number sections bare (**1.1** "Affiliate" means...)
never carry the synthesized kind word, so anchorDisplayLineStart's
startsWith(display) could never match them - whole contract corpora
measured 0% addressable (tax-TP 0/25 docs) and the no-fit band was
closed to the index arm. Adds a digit-led numeral variant with a token
boundary (1.1 never claims 1.10), accepted only when the line continues
heading-like or carries the section's known title (normalized compare,
bold/smart-quote tolerant), plus the pandoc '---' em-dash rendering as a
heading continuation.

No-fit corpus addressability before -> after: CoC 67%->80%, antitrust
10%->100% (of extractable docs), diligence 57%->93%, indenture 68%->72%,
tax-TP 0%->87%. Anchor oracle over all 5 corpora: 5,482 anchors, 0
regressions (3 flagged ARTICLE anchors reproduce on the pre-fix module;
2 new flags are correct-row table-derived subsections reached only
because their parents now anchor). Fit-band invariants probe green
(prefix/kind-filter/idempotency), structureIndexExperiment +
localToolWiring suites 39/39, tsc clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pfUVhNFTRvhYGXBwoKNj6
421f6dd5 docs(lab): ledger - Phase D gate 50/57, adversarial audit digest, anchor oracle verdicts, Eli directives Eli Ziff 2026-08-06 ↗ GitHub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pfUVhNFTRvhYGXBwoKNj6
56b0da47 docs(lab): ledger - sweep rows 1-2 judged; acq coverage-accounting defect diagnosed Eli Ziff 2026-08-06 ↗ GitHub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pfUVhNFTRvhYGXBwoKNj6
cd34e1cf docs(lab): full 11,293-doc oracle sweep mined - oob=0, veto genres, 294 tracked-changes docs, native 2,300-doc diligence haystacks Eli Ziff 2026-08-06 ↗ GitHub
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pfUVhNFTRvhYGXBwoKNj6

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-833.md from inside the repo you want the changes in.

⬇ Download capture-thread-833.md