Skip to content

fix: identity matcher rebuilt against a frozen adversarial corpus (3rd wrong-patient reopening) + armed-coverage metric#16

Merged
abrichr merged 14 commits into
mainfrom
feat/identity-roc
Jul 10, 2026
Merged

fix: identity matcher rebuilt against a frozen adversarial corpus (3rd wrong-patient reopening) + armed-coverage metric#16
abrichr merged 14 commits into
mainfrom
feat/identity-roc

Conversation

@abrichr

@abrichr abrichr commented Jul 10, 2026

Copy link
Copy Markdown
Member

fix: identity matcher rebuilt against a frozen adversarial corpus — near-name siblings (3rd wrong-patient reopening) + armed-coverage metric

The vulnerability (confirmed, reproduced)

band_match returned (coverage=1.0, residue=0)VERIFIED — for all of:

  • Belford, Phil 1985-03-12 M vs Belford, Philip 1985-03-12 M (containment tier: 'Phil' ⊂ 'Philip')
  • the reverse direction (similarity tier, ratio 0.8)
  • Smith, John ... vs Smith, Joan ... (similarity tier: 0.75 ≥ 0.7)
  • Belford, Phil ... vs Belford, Phillipa ... (containment tier)

This is the wrong-patient P0 reopening a third time (pixel-lookalikes → bands; residue-blind coverage + param disarm → token matcher + residue cap; now near-name siblings). Real EMR rows are full of near-name siblings, and downstream note verification does NOT catch a wrong-patient write — the note really is saved, in the wrong chart.

1. Held-out corpus FIRST, frozen before any evaluation or fix

To break the fix-against-the-probe cycle, commit 1 of this branch adds openadapt_flow/validation/adversary_corpus.py: a deterministic seeded generator (seed 20260710) of 4360 labeled pairs — 2200 different_entity (prefix-extension names, single-letter edits, transpositions, Jr/Sr/II suffixes, same-surname/-first siblings, shared clinical text, DOB off-by-one-field, MRN digit swaps, adjacent-row mixtures) and 2160 same_entity (OCR confusions, token splits/joins, dropped short tokens, case/whitespace jitter, segment reordering, occlusion, spurious tokens, compound noise). The sha256 manifest is committed and pinned by tests, so post-hoc tuning of the corpus toward the matcher is detectable in git history. The generator was not modified after first evaluation (no generator bugs found; byte-identical to the pre-evaluation commit).

2. Measured, then fixed, operating point from the ROC

Current (pre-fix) matcher on the corpus: 53.9% false-accept overall. By category: DOB off-by-one 99.1%, Jr/Sr 99.1%, single-letter edits 98.2%, transpositions 95.5%, prefix extensions 72.3%, MRN swaps 50.0%, same-surname 15.5%. False-abort 12.1%.

The rebuild (runtime/identity.py): tokens match ONLY on OCR-equivalence (canonical identity under real engines' confusion classes: l/1/i, O/0, 5/s, 2/z, 8/b, 9/g, rn/m, cl/d, vv/w) plus full-consumption splits/joins — the containment and 0.7-similarity tiers are gone. Unmatched tokens split into absence (uncovered runs, budgeted as before) and contradiction (near-miss ≥ 0.62 on canonical forms, semantic containment with alphabetic residue, replacement by an unexplained observed token, generational suffix on one side) with a zero budget.

Post-fix: 0.000% false accepts (0/2200), 10.69% false aborts (better than before on both axes). ROC swept over contradiction_sim × coverage × run_cap × contradiction_cap; chart + tables + raw sweep committed under docs/validation/ (identity_roc.png, IDENTITY_ROC.md, identity_roc.json).

Operating point, weighting said out loud (false accept = wrong-patient write, catastrophic; false abort = ~$0.10 hybrid fallback; priced at 4+ orders of magnitude): coverage 0.8 / run_cap 4 / sim 0.62 / contradiction_cap 0. Deliberately NOT the Pareto-min-false-abort corner (0.7/8, FAbort 7.96%): that corner's zero rests entirely on the contradiction rule (evade it → FA 60.8%), while at 0.8/4 the older budgets independently stop 79.5% — defense in depth, bought with 2.7pp of false aborts concentrated in occlusion shapes whose identity tokens were never read (refusing those is correct).

Pins: all four sibling probes as permanent mismatch tests; operating-point boundary tests; a corpus-wide zero-false-accept regression test (a rate, not a probe list) + 12% false-abort budget. True-row live shapes (OpenEMR modal permutation, OCR jitter, splits/joins, MockMed fixtures) still verified.

3. Protection coverage as a first-class metric

Identity verification covers ONLY armed steps — live OpenEMR checks armed 4/12; a fresh MockMed demo bundle arms 1/8. Now auditable everywhere:

  • workflow.json: per-step identity_armed / identity_unarmed_reason (concrete reasons: no readable band text / only the target's own label / too generic after volatile filtering) — auditable before running.
  • REPORT.md: "N of M click steps identity-armed" + unarmed steps by id/intent/reason, computed over the whole bundle at run start. Verified end-to-end via CLI record→compile→replay.
  • Benchmark generators (MockMed + OpenEMR + hybrid): compiled-arm rows/aggregates carry the metric; methodology sections render it. Committed BENCHMARK.md files' results.json predate the metric and carry an explicit "not captured in this results.json" note (no fabricated numbers).
  • docs/LIMITS.md: the dangerous list now LEADS with the coverage gap; wrong-entity section updated for the rebuild.
  • docs/validation/VALIDATION.md: 2026-07-10 fix update — third reopening said plainly with probe strings, corpus methodology, before/after per category, ROC + operating point rationale, coverage surfaces.

Verification

  • Full suite green post-merge with main (unit + perturbation/chaos/primitives/record-compile-replay e2e): see checks.
  • Zero Anthropic API calls anywhere (ANTHROPIC_API_KEY unset throughout).
  • Real-flow verification: CLI demo-record → compile → replay produces REPORT.md with the coverage section and per-step reasons; bundle JSON carries the audit fields.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ

abrichr and others added 8 commits July 10, 2026 11:53
Deterministic, seeded generator (seed 20260710) of 4360 labeled
(recorded_band, observed_band) pairs: 2200 different_entity (the
false-accept side: prefix-extension names, single-letter sibling
edits, transpositions, Jr/Sr suffixes, shared clinical text, DOB
off-by-one-field, MRN digit swaps, adjacent-row mixtures) and 2160
same_entity (the false-abort side: OCR confusions, splits/joins,
dropped short tokens, case/whitespace jitter, segment reordering,
occlusion, spurious tokens, compound noise).

Frozen BEFORE evaluating or touching the matcher: the sha256 manifest
is committed (docs/validation/adversary_corpus_manifest.json) and
pinned by tests, so any post-hoc tuning of the corpus toward the
matcher is detectable in git history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
…rd P0 reopening)

Confirmed vulnerability: band_match returned (coverage=1.0, residue=0)
— VERIFIED — for sibling rows: 'Belford, Phil' vs 'Belford, Philip'
(containment tier), the reverse, 'Smith, John' vs 'Smith, Joan'
(similarity tier, 0.75 >= 0.7), and 'Belford, Phil' vs 'Belford,
Phillipa'. On the frozen adversarial corpus the legacy matcher's
false-accept rate was 53.9% overall (DOB off-by-one 99.1%, Jr/Sr 99.1%,
single-letter edits 98.2%, transpositions 95.5%, prefix extensions
72.3%, MRN digit swaps 50.0%).

The rebuild:
- token matching accepts ONLY OCR-equivalence — identical after
  canonicalizing real OCR confusion classes (l/1/i, O/0, 5/s, 2/z, 8/b,
  9/g, rn/m, cl/d, vv/w) — plus full-consumption token splits/joins.
  The partial-containment and 0.7-similarity tiers are gone: both
  accepted semantic extensions of name tokens.
- unmatched tokens split into ABSENCE (uncovered runs — OCR dropout,
  budgeted as before) and CONTRADICTION (near-miss similarity >= 0.62,
  semantic containment with alphabetic residue, replacement by an
  unexplained observed token, generational-suffix presence on one side)
  with its own zero budget.

Operating point picked from the ROC on the frozen corpus (sweep of
contradiction_sim x coverage x run_cap x contradiction_cap, before/
after chart + tables committed under docs/validation/): coverage 0.8,
run cap 4, contradiction_sim 0.62, contradiction cap 0 -> false accept
0.000% (was 53.9%), false abort 10.69% (was 12.1%), NOT the Pareto-min
false-abort corner: at cov 0.7/run 8 the zero rests entirely on the
contradiction rule (FA 60.8% if it is evaded) whereas at 0.8/4 the
older budgets independently catch 79.5% — defense in depth over 2.7pp
of availability concentrated in unreadable-name occlusion shapes.

All four sibling probes pinned as permanent mismatches; operating point
pinned by boundary tests; corpus-wide zero-false-accept regression test
added. Full unit suite green (364), true-row live shapes (OpenEMR modal
permutation, OCR jitter, split/join) still verified.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
Identity verification covers ONLY armed steps, and real bundles arm a
minority (live OpenEMR checks armed 4/12; a fresh MockMed demo bundle
arms 1/8). That fact was previously a buried sentence in a live-check
note; an unarmed click proceeds with NO identity check at all. Now:

- workflow.json: per-step identity_armed / identity_unarmed_reason
  written by the compiler (with the concrete reason: no readable band
  text / only the target's own label / too generic after volatile
  filtering) so an operator can audit protection BEFORE running.
- REPORT.md: every run report states 'N of M click steps
  identity-armed' and lists the unarmed steps by id, intent and reason
  (computed over the whole bundle at run start, not just executed
  steps; pre-metric bundles get an honest fallback reason).
- Benchmark generators (MockMed + OpenEMR): compiled-arm rows and arm
  aggregates carry the coverage; BENCHMARK.md methodology sections
  render it. The committed BENCHMARK.md files' results.json predate the
  metric, so they carry an explicit 'not captured in this results.json'
  note instead of fabricated numbers.
- docs/LIMITS.md: the dangerous list now LEADS with the coverage gap,
  and the wrong-entity section is updated for the 2026-07-10 matcher
  rebuild (near-name siblings, corpus rates, occlusion-abort rationale).
- docs/validation/VALIDATION.md: 2026-07-10 fix update — the third
  wrong-patient reopening said plainly with the four probe strings,
  frozen-corpus methodology, before/after rates per category, ROC
  operating point with the stated cost weighting, and the coverage
  metric surfaces.

Verified end-to-end: CLI demo-record -> compile -> replay produces a
REPORT.md with '1 of 8 click steps identity-armed' and per-step reasons;
e2e CLI smoke test now asserts the section and the bundle fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
The hybrid generator (PR #14, merged after this branch forked) reuses
_compiled_run and _arm_aggregate, so its compiled-arm rows and
aggregates already carry the identity-coverage fields; this renders
them in the BENCHMARK.md methodology section. The committed
benchmark/hybrid/results.json predates the metric, so the regenerated
BENCHMARK.md carries the explicit not-captured note (verbatim
regeneration verified — one-line diff).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
All 13 probes VERIFY against the shipped matcher at the shipped
operating point (reproduced locally, wrong-patient direction); they are
committed FIRST, asserting mismatch, so the acceptance criteria for the
matcher redesign are on record before the redesign or corpus v2:

- Blocker 1: confusion-collided distinct names (Neil/Nell, Clay/Day,
  Marnie/Mamie, Gail/Gall) — the v1 corpus excluded this class by
  construction, so its 0.000% headline was partially tautological.
- Blocker 2: sub-MIN_BLOCK tokens invisible to contradiction (middle
  initial, SEX column, 2-char names).
- Blocker 3: observed-side superset always verifies (appended tokens,
  two-row merge, wrong row mentioning the recorded patient).
- Major 4: fully absent 4-char name at the run cap verifies with the
  identity token never read.

Safe-direction pins (hyphenated split, Bob/Robert, Alison/Allison,
MRN/DOB edits, digit-class homoglyphs, param-mode raw-run rejection of
Neil->Nell) pass today and must keep passing. The Ann Marie/Annmarie
join edge is pinned as a disclosed residual.

The 13 probe tests FAIL at this commit by design; they pass after the
matcher redesign lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
…ruction

Versioned extension of the frozen corpus (v1 generator and manifest are
untouched, history intact): own seed (20260711), own SHA manifest,
committed BEFORE the redesigned matcher is evaluated on it — the same
freeze discipline as v1, so the corpus-v2 commit precedes the
matcher-fix commit in git history.

2240 pairs across the reviewer-identified excluded classes:

- different_entity (1590): confusion-collided names generated
  systematically from the letter-letter members of the frozen confusion
  table over the v1 name lists (name-only / realistic distinct-IDs /
  identical-IDs probe shape), middle initial, sex column, 2-char names,
  observed-superset shapes (appended name, merged second row, title/cc
  row mentioning the recorded patient), and absent 4-char name tokens.
- indistinguishable (200) — NEW third label: the true row misread by a
  letter-letter confusion, textually identical to its different-entity
  twin. ABORT is the correct outcome for both readings; scoring counts
  abort as justified (not a false abort) and verify as a false accept.
- same_entity (450): the availability side the new budgets must not
  kill — digit-class-only OCR noise (names contain no digits, so no
  collision is possible), lowercase adjacent-row bleed, hyphenated
  surname splits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
…blockers

All 13 reviewer probes (committed first in
tests/test_identity_out_of_corpus.py, failing) now pass; corpus v2 was
frozen in the preceding commit, before this change was evaluated on it.

Four new decision budgets, all zero-tolerance at the operating point:

- SUSPECT chars (Blocker 1): a name-plausible token matched ONLY by a
  letter-letter confusion equivalence (Neil/Nell i-l, Clay/Day cl-d,
  Marnie/Mamie rn-m) is indistinguishable from a real sibling — the
  honest outcome is an abort for BOTH readings, corroborating identical
  MRN/DOB notwithstanding (the probes pin exactly that). Digit/symbol
  confusions ('Phi1', '5ample') stay clean: names contain no digits, so
  no collision with a different name is possible. This ports the spirit
  of param mode's raw longest_run check (which already rejected
  Neil->Nell) into context mode.
- Short-token replacement (Blocker 2), COUNT-based: a replaced 1-2 char
  alphabetic token (middle initial J->K, SEX column M->F, 2-char names
  Al->Bo) is contradiction. Multiset accounting, because a replaced
  initial can duplicate the sex letter and look 'explained' per-pair.
- Unexplained observed name-shaped tokens (Blocker 3): context mode
  gains the observed-side budget param mode always had — appended
  middle names, two-row OCR merges, and message/cc rows that merely
  MENTION the recorded patient all refuse; lowercase adjacent-row bleed
  stays exempt (the legitimate spurious class, 0% false aborts on v2).
- Absent name-like token (Major 4): a fully absent 4+ char alphabetic
  token refuses even inside the generic run cap — identity must not
  verify with its identity token never read. Trailing-numerics dropout
  keeps the old tolerance (class-weighted, not blanket). The old pin
  test_pure_absence_boundary_at_run_cap is FLIPPED accordingly.

The replayer now extracts the LIVE band exactly as the compiler
extracted the recorded band (target's own crop excluded at the resolved
point, volatile lines dropped against the replay date): the previous
asymmetry meant the label and live clock cells appeared as observed-side
extras, which is what made an observed-superset budget impossible.

Measured (frozen corpora, regression-netted in
tests/test_identity_corpus_rates.py): 0 false accepts across v1 (2200
wrong-entity pairs), v2 (1590 wrong-entity + 200 indistinguishable),
and the 13 probes; v1 false aborts 21.2% (up from 10.7% — the
availability bill of closing the blockers, per-class breakdown in the
regenerated IDENTITY_ROC.md), v2 legitimate-noise classes 0.0%;
indistinguishable class 200/200 abort. Letter-letter jitter on the true
row now aborts (pinned; disclosed) — the flipped twin of the Neil/Nell
fix. Full suite green including e2e (43/43 live record-compile-replay).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
abrichr and others added 5 commits July 10, 2026 13:36
…st limits

Re-run the full ROC on corpora v1+v2 with three-label scoring
(different_entity / same_entity / indistinguishable) and the six-budget
decision sweep; re-picked operating point keeps 0.8/4/0 and adds
suspect=0, unexplained-name=0, absent-name-cap=3:

- FA 0.000% across v1+v2 (3990 wrong-entity/indistinguishable pairs) —
  every number explicitly scoped to 'corpus v1+v2 plus the 13
  out-of-corpus probes', with the operating-point-fit limitation stated
  plainly (freezing prevents tuning the corpus toward the matcher, not
  the thresholds toward the corpus; v1's zero was shown partially
  tautological one review ago and the same criticism applies to v2's).
- FAbort v1 21.2% / v2 0.0%; indistinguishable class 200/200 abort.
- The cheaper zero-FA Pareto corner (cov 0.85 / run 8 / absent-name
  off, FAbort 15.86%) is rejected with an empirical counter-example:
  its Major-4 protection is a band-length artifact (the same absent
  4-char name at coverage 0.915 verifies there; the absent-name cap
  refuses structurally).
- Occlusion recount CORRECTS the earlier framing: 102/216 occlusion
  aborts at the shipped decision (107/224 at production) still had
  BOTH name tokens readable — trailing DOB/MRN loss, an availability
  cost, not the 'correct epistemic refusal' previously claimed.
  VALIDATION.md's original sentences carry strikethrough corrections.
- Realistic-exposure analysis: the Blocker-1 probes used identical
  MRNs (unrealistic); with differing readable IDs the absence/
  contradiction budgets catch 180/180 without the suspect rule; the
  true residual exposure is name-as-only-discriminative-token bands,
  where the suspect rule is the only defense and covers only the
  frozen confusion table.
- LIMITS.md restores and EXPANDS the honest disclosure this PR had
  deleted ('names within OCR-jitter distance verify'): the residual
  verify classes are now listed plainly (Ann Marie/Annmarie join,
  case/whitespace-only differences, 1-2 char letter-letter confusions,
  added short tokens), plus the permanent indistinguishable-class
  aborts and the ~21% compiled-only availability price.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
Second adversarial review of PR #16 found a 5th wrong-patient P0
reopening: the round-3 suspect budget guards NAME tokens only
(_name_plausible is False for any token containing a digit), so the rule
was OFF for MRNs/account numbers while confusion canonicalization (l/1,
O/0, S/5, Z/2, B/8, g/9) still applied to them. A different patient's
alphanumeric identifier differing only by one letter/digit-confusable
char silently VERIFIED, defeating MRN-based disambiguation of same-name
patients.

Committed FIRST, FAILING, as acceptance criteria (reproduced locally):
- probes 14-16: MRN/Acct l/1, O/0, S/5 confusions verify (must abort)
- probe 17: two same-name patients, MRN the sole discriminator, one
  confusable char apart -> verify (the canonical clinical case; must
  abort regardless of name raw-match)
- probe 18: same hole fires in param mode (MRN as parameter)
- availability-cost boundary: true-row MRN OCR noise (A01234->AO1234)
  must abort under the chosen safety-first design (documented cost)

Controls that must keep passing: all-digit MRN diff (748291 vs 748292)
mismatches via coverage not suspect; raw-equal MRN with name-side digit
noise ('Belford'->'Be1ford') still verifies (the fix is scoped to
RECORDED identifier tokens, not any observed digit).

The 6 new failing tests pass after the identifier-suspect fix; corpus
v3 is frozen before that fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
Versioned extension (v1/v2 untouched, history intact): own seed
(20260712), own SHA manifest, committed BEFORE the identifier-suspect
matcher change is evaluated on it — same freeze discipline, so the
corpus-v3 commit precedes the matcher-fix commit.

300 different_entity pairs, one class id_letter_digit_collision: two
entities identical in every token EXCEPT an alphanumeric identifier
(MRN/account/chart ref) differing by exactly one letter/digit-confusable
position (l/1, i/1, o/0, s/5, z/2, b/8, g/9), generated systematically
from the confusion pairs. This is the class v1's mrn_digit_swap could
not surface: v1 only swapped/changed DIGITS (748291 vs 748292), which
are never in one confusion class. A VERIFY here is a wrong-patient
action — the identifier is the sole discriminator and is exactly what
MRN-based disambiguation relies on.

The generator renders one row template per pair and formats it with each
identifier, so the identifier is provably the only differing token
(pinned by test_v3_pairs_are_confusion_equivalent_and_id_only_differ).
No same-entity identifier-noise class is added: under the chosen
safety-first design all confusion-differing recorded identifiers abort,
so such a label would be unwinnable by construction; the availability
cost is measured directly on v2's digit_confusion_true_row class.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
…atient reopening

The round-3 suspect budget guarded NAME tokens only: _name_plausible is
False for any token containing a digit, so the rule was OFF for
MRNs/account numbers while the confusion canonicalization (l/1, O/0,
S/5, Z/2, B/8, g/9) still applied to them. A DIFFERENT patient's
alphanumeric identifier one letter/digit-confusable char apart
('A01234' vs 'AO1234') silently VERIFIED, defeating MRN-based
disambiguation of same-name patients (verified in param mode too).

Fix: _suspicious_pair now also returns True when the RECORDED token
contains a digit (an identifier matched only across a confusion). A
confusion-only match on such a token is charged to the zero suspect
budget -> abort. Chosen design is option A of the review (no
corroboration escape): a confusion-differing identifier aborts even when
name and DOB raw-match, so two same-name patients distinguished ONLY by
an OCR-confusable identifier char never verify. Option B (allow if
name+DOB corroborate) was rejected because two real patients can share a
name and DOB, so the MRN is the sole unique key and B would re-admit
exactly the Doe John wrong-patient case.

Scoping on the RECORDED token is what keeps name-with-digit-noise
verifying while identifier-with-digit aborting: the recording carries
the ground truth of the token's type. 'Belford' -> 'Be1ford' is clean
(recorded all-alpha = name); 'A01234' -> 'AO1234' aborts (recorded has a
digit = identifier). All-DIGIT differences (748291 vs 748292) are not
confusion-equivalent and mismatch via coverage/contradiction as before.

Measured (frozen v1+v2+v3, regression-netted): 0 false accepts across
all three including v3's 300 id_letter_digit_collision pairs and the 18
out-of-corpus probes. Availability cost, honest: true-row identifier OCR
noise now aborts (indistinguishable at band level) — v2
digit_confusion_true_row 0% -> 48.7%, v1 overall 21.2% -> 28.2% (budgets
updated). Residual verify: short 1-2 char all-alpha codes confused with
a digit (recorded token has no digit; under the 3-char name floor).
Full suite green including e2e (43/43).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
…isclosures

Re-run the full ROC on corpora v1+v2+v3 (6900 pairs) after the
identifier-suspect fix; operating point confirmed (same six/seven caps,
FA 0.000% / FAbort 26.17% / indistinguishable-abort 100% across all
three). New content:

- IDENTITY_ROC.md: a '5th reopening' section (the identifier
  letter/digit collision, chosen design A with the option-B rejection
  rationale, the RECORDED-token scoping, and the honest true-row
  availability cost); a v3 per-category table (id_letter_digit_collision
  legacy 100% -> 0.0%); the realistic-exposure table gains the v3 row
  (300/300 verify without the suspect rule, 0 with it) and CORRECTS the
  first review's 'ids differ -> 180/180 without the suspect rule' claim
  as name-collision-only (it did not cover the letter/DIGIT identifier
  case). Scope re-stated as v1+v2+v3 plus the 18-probe set.
- LIMITS.md: the contradicted-list 'swapped MRN digits' is qualified to
  'all-DIGIT' (the letter/digit case is now a suspect, not a
  contradiction); the suspect budget is described as name AND identifier;
  the residual-verify list gains the short all-alpha-code case and the
  true-row-identifier-noise availability cost; the halt price is updated
  21% -> 28%; every zero-claim re-scoped to v1+v2+v3 + 18 probes.
- VALIDATION.md: the realistic-exposure bullet gets a second-review
  caveat, and a new 'SECOND review / 5th reopening' subsection records
  the hole, the fix, corpus v3, and the availability cost.

No claim left standing that the final matcher falsifies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
@abrichr abrichr marked this pull request as ready for review July 10, 2026 18:30
@abrichr abrichr merged commit dae28ed into main Jul 10, 2026
2 checks passed
abrichr added a commit that referenced this pull request Jul 11, 2026
…ct stale-code finding

The branch was originally cut from a stale local main that predated the
identity-matcher fixes (#16/#17/#19), so the compiled arm ran against the
pre-#16 matcher and recorded 3 sibling wrong-actions — a stale-code artifact.
Rebased onto current main and re-ran the identity-sensitive cells: the
compiled arm now safe-halts both the near-lexical sibling (Sorenson/Sorensen)
and the decoy, 3/3 each — 0 identity wrong-actions. The browser identity
fixes transfer to desktop-rendered OCR text. Narrative corrected in
BENCHMARK.md; non-identity findings (UIA-tree gap, DPI/theme defeat vision but
halt-not-miswrite, full prlctl automation) unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CKrVJJy5jWVCkXAqgUqtqZ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant