Files
esh-pfi-infrastructure/persistent-memory.d/2026-09-17-mccarthy-split-name-leak.md
T

5.8 KiB
Raw Blame History

[2026-09-17] The leak gate passed with five protagonist names still in every copy

Found during D4 pre-flight, three stages downstream of where it happened. Commit c559664.

leak gate, 2026-09-17 morning   0 of 75 renameable, 0 of 37 sub-threshold, both controls green
actually present, all 6 copies  Bell x2  Chigurh x3  Moss x2  Toadvine x4  Glanton x2

The mechanism

leak_gate.py scans \b(Surface)\b. A character inserted inside a name defeats that pattern outright, so a mangled occurrence is not merely unrepaired — it is unrenameable by rename.py and unreportable by the gate, and the gate prints a clean zero over it. Two extraction artifacts produce exactly that:

B ell   C higurh   M oss   T oadvine     a small-caps drop cap kept as its own token
Toad-vine   Glan-ton                     a print line-break hyphen kept by the extractor

⭐ Every VISIBLE occurrence had been renamed correctly — exact-match survivors were 0, as the gate said. That is what makes this residue invisible to a spot-read: the names are gone everywhere you look. Bell sits in the entity map at 147 capitals, Glanton at 365.

This is the third member of a family. lv-bronte's was _Antigua_ (_ is a word character, so \bAntigua\b cannot match inside it), found by hand in 2026-09-16 and never generalised. The generalisation is the point: any separator inside a name blinds a word-boundary scan.

Fixed at three levels, and all three must stay

  1. build_corpus_mccarthy.py rules 4 and 5 repair the source text — 32 split initials with a lowercase remainder (rule 1 requires a following ALL-CAPS word and DROPCAP requires two, so this is the class both leave behind), 5 hyphen-split names by name. Both carry expected counts so a master change fails the build. ⚠ Rule 4's letter class is consonants only. I opens 1,966 paragraphs (the pronoun), A opens 143 (the article), Y opens 32 (Spanish y). Folding any of them would corrupt 2,141 lines to fix 32 — the same I/A trap that bit audit_stoplist.py.
  2. leak_gate.py runs a separator-tolerant pass every time, with its own positive and negative controls, and it fails the gate. Validated against the pre-fix tree: reports all five surfaces, exits 1.
  3. The exact-match passes are untouched, so the old verdict is reproduced alongside the new.

⚠ The fragment filter is what makes the new pass usable. A naive separator-tolerant scan is dominated by false positives — on Hemingway it returns 21 hits of which 18 are ordinary text (God damn for the surface Goddamn ×14, plus I run, On an, Do me, Si le). The discriminator, with no dictionary: in a genuine split at least one FRAGMENT is not a word of this corpus. God and damn occur constantly; Primi, tivo, ell, higurh, Toad do not. That one test cleared all 18 and kept all 3 real ones.

⚠ My first negative control could not pass. It planted the split nonce in its own probe text and then asserted the nonce was absent — an alarm wired to itself, failing on every run. It now hunts the split nonce in the real copies. A control that cannot pass is not a control.

The shipped corpora, checked with the committed instrument

Re-derived with the COMMITTED gate, not a scratch probe:

lv-bronte      GATE PASSED   0 separator-split survivors          (9.7 s)
lv-hemingway   GATE FAILED   Pasionaria, Primitivo, Chicote       (34.6 s)
                             1 occurrence each per copy, in all 6 copies — SHIPPED and LIVE

⚠ The first version of this scan was too slow to run on Hemingway — per-surface scanning is O(surfaces x copies x corpus) and 881 surfaces x 10 copies was still going at 5 minutes when it was killed. Rebuilt as one alternation pass, same trick scan() already used: 35 s, identical verdict and identical hit counts on both McCarthy trees. A gate too slow to run is not a gate.

Operator call outstanding on whether 3 names in a 958k-word corpus warrant re-gating and retraining a live adapter. Not acted on.

The chain was recovered, not remembered — and is now written down

There was no McCarthy runbook, and the D1→D3 session issued its commands over non-interactive ssh so no shell history survived. The chain was recovered by rebuilding candidates and matching sha256 against the artifacts on disk, then pinned:

D1  build_corpus_mccarthy.py                        6 works byte-identical
D2  entities.py --min-count 5 --fold-clitics --drop-acronyms --min-mid-ratio 0.2 --min-mid 2
D2c apply_gender_overrides.py                       entities-final.json byte-identical
D3  rename.py --preset mccarthy --scope corpus --min-cap 5 --copies 6 --seed 4919
                                --holdout-chapter 7 17     all 36 copy files byte-identical

⚠ --min-mid-ratio is what keeps Yeah/Buenas/Shh/Sí out of the map. The map is insensitive to it: any value in [0.05, 0.3] with --min-mid 1 or 2 reproduces byte-for- byte; --min-mid 3 does not. The original values are unrecoverable and it does not matter — which is worth saying, because an exact-looking recipe that was never pinned invites a false claim of reproduction. Full recipe and every deviation: scripts/mccarthy-corpus/RUNBOOK.md.

⚠ D1 must run on nh3-dev — the builder reads the kvasir catalogue by absolute path and gx10 has no copy. The previous session's "on gx10" note is true of D2 onward only.

⚠ No phrase map exists for this corpus, so the gate's phrase audit does not run at all. Yarros and Brontë both had one. Not closed.

Rollback: ~/lv-mccarthy/corpus-{clean,renamed}.pre-splitfix on gx10.

Related: 2026-09-17-mccarthy-d1-d3, 2026-09-17-beat-contamination-leak, 2026-09-17-lv-hemingway-gate, 2026-09-17-lv-bronte-gate.