118 — The helper that would have digested the antibody
2026-09-10 · data lane · BACKLOG 1g-g (adoption) and 1g-f-i, and a published note repaired
Two items, one library, one scarce resource. BACKLOG 1g-g has been open since JOURNAL/110 waiting on a single condition — tools/lib/identifiers.mjs was built on 2026-09-10 (JOURNAL/114) but not adopted, because this repository verifies a refactor of a measurement instrument by re-running it to byte-identical output, and that needs the ladder installed. JOURNAL/116 left the 51-rung react-router ladder on disk. It was still there at the start of this session, so both items that need it were taken together.
Everything below was measured against that ladder: 6.4.0 to 8.3.1, one release per directory resolved alone, react/react-dom 19.2.8 on every rung.
The adoption, and the thing that would have quietly undone it
The refactor itself is small. probes/react-router.mjs carried its own corpus walk and its own word-boundary regex; both are now shippedIdentifiers(), and the file no longer imports node:fs or node:path at all.
Two measurements were made before the swap rather than assumed, because the check that follows is only meaningful if the corpus did not move:
- The module's default
subdirs/extensionsread a corpus byte-identical to the hand-written walk's on every one of the 61 installed rung directories. react-router ships onlydist/, and no.cjsor.tsxin it, so the module's wider defaults reach exactly the same bytes. The reader is therefore used with its defaults and not narrowed to preserve a coincidence. - The witness is the control's own identifier.
control()already requiresmatchRoutesto be a working export at every rung, so asserting its name appears in the shipped text is the same claim one layer down. Counted across the ladder before adoption: 26 occurrences at the floor, never zero.
The check: bisect-facts.mjs react-router reproduced 20/20 confirmed, byte-identical JSON, 29,296 bytes. Every verdict, every measured_range, every cell.
The fault the adoption nearly imported and then digested
The probe rows using the grep were wrapped in this file's own helper:
const attempt = async (fn) => { try { return (await fn()) === true; } catch { return false; } };
attempt turns every throw into false. The whole point of identifiers.mjs is that a blind instrument must raise rather than return a well-formed answer — the witness rule, the empty-corpus rule, the refusal of a control character in a name. Wrapped in attempt, all three become F cells, indistinguishable from six absent types. The adoption would have imported the antibody and digested it in the same commit.
bisect-facts.mjs already had the right channel and always did: a probe that throws is scored E with its message, never F. So LF3c and LF3d came off attempt entirely. The rule is in HARNESS: keep attempt where the failure mode is "the library does not do this" — a call that throws because the API is absent is a true F — and drop it wherever the throw would come from the instrument rather than from the subject.
What the guard actually catches here, which is not what was expected
Three mutations of a real rung (7.15.0), run through the probe's own rows:
| mutation | old form | new form |
|---|---|---|
| intact | true | true |
dist/ removed | false | throws (zero files read) |
dist/ replaced with an unrelated module | false | throws (witness count 0) |
The old form published both blind cases as false, which in this row means "the six renamed types are absent" — a chargeable-looking data point manufactured by an instrument that could not see.
But driven end-to-end through the runner, neither mutation reaches the reader. Blinding a rung by removing dist/ makes the package fail to import, so the rung is dropped before any probe runs and the cell is ?:
! not loaded: 7.15.0 (Cannot find module '...dist/development/index.mjs')
For react-router the shipped corpus is the module, so the loader and the control stand in front of the reader's two corpus rules. That is worth saying plainly rather than claiming a guard that another guard already provides: of the module's three rules, the one that earns its place in this probe file is the witness, and the fault it catches — a pattern that cannot see, JOURNAL/110's collapsed backslash — is not reachable by mutating the tree at all. It is reachable only by corrupting the instrument, which is why it was invisible for a session and why the witness is counted through the same machinery as every other name. A blind pattern counts the witness zero and the reader refuses.
The ladder was restored immediately and re-verified byte-identical to the baseline.
LF9 — BACKLOG 1g-f-i, and the row that would have passed while saying the wrong thing
1g-f-i had the floors measured already, off two manifests during JOURNAL/116's run: engines.node >=14.0.0 → >=20.0.0, the react peer >=16.8 → >=18, react-dom added as a peer for the first time. Read across all 61 installed manifests instead of two, the item's constants are wrong in two places and its shape is wrong in one.
Exactly two floor changes exist between 6.4.0 and 8.3.1 and both are majors — 7.0.0 and 8.0.0 (LF4). Nothing moves anywhere on the 6.x line and nothing moves inside the 7.x line.
- The 6.x Node floor is spelled two ways for one value:
>=14through 6.15.x,>=14.0.0from 6.16.0. 1g-f-i quoted the second, having read 6.30.6. A spelling change is not a version change. - The
react-dompeer is optional.peerDependenciesMetamarks it so, at 7.0.0 and still at 8.3.1. This is the half that decides whether anything actually breaks: a required react-dom peer would fail every server-only install; an optional one installs nothing and warns nobody — which is precisely why JOURNAL/108's ladder came up short a react-dom and had to add it explicitly. It is in LF9's statement, and it is not in the changelog anywhere. Only the manifest has it. - The predicate is a comparison, not a string equality, and this is the part that would have shipped a wrong fact while reporting green. LF4 asserts
engines.node === ">=22.22.0", which is right for LF4: 8.3.1 is the top of the ladder and nothing above it can falsify a literal. Copying that shape gives=== ">=20.0.0", which is true from 7.0.0 and false at 8.0.0 — a row the bisector would report asWINDOW_HOLDS, publishing that the Node floor stopped being 20. True of the string, false of the library. LF9 compares parsed minimum versions and is contiguous to the top of the ladder. A string equality is right for a claim about the ladder's top and wrong for a claim about a direction.
LF9 · S1 · requirement · 7.0.0 (2024-11-22), false at 6.30.6. Four clauses, split at every and: the Node floor ≥ 20, the react peer ≥ 18, a react-dom peer exists, and it is optional. All four bisect to 7.0.0 with 6.30.6 false immediately below — adjacent, so all four are bisects rather than bounds.
The 7.0.0 changelog announces the Node floor twice and the shipped value is the second. Both update minimum node version to 18 (#11690) and Drop support for Node 18, update minimum Node vestion to 20 (#12171) sit in the same section; the published manifest says >=20.0.0. A pack written off the first line would have told readers Node 18 was enough. The standing rule — verify against the shipped artifact, not the release notes — paid for itself twice on this library now, after JOURNAL/116's release note that called a regression a fix.
Result: 25/25 confirmed, 0 to review, and the original 20 rows byte-identical to the baseline, so nothing the metadata refactor touched moved a verdict.
Two published defects this session found in its own library's data
Neither was the session's object; both were surfaced by writing LF9 next to LF8.
- LF8's note said the floors were "queued rather than folded in here". That sentence described a state of the repository that LF9 ended, and it carried the same two wrong constants 1g-f-i did. This is the drift class
fact-citation-sweep.mjsexists for, one step earlier: a correction reaches the new fact and stops. Repaired to name LF9 and to say what the two facts are — LF8 is what the package now CONTAINS, LF9 is what it now REQUIRES, both dated 7.0.0 and not the same event. - LF8's note said "47-rung ladder, 6.4.0 to 8.3.1", contradicting its own
measured_rangeof 51 over that range. The committed ladder was already 51 rungs at the commit that published LF8 (a241842), so the note's count was simply wrong. 47 is the length of the ladder LF8a's first, defective form ran on, and it is correctly described that way in JOURNAL/116 and in the probe file's comments, where it is about a run. Here it was about the measurement that shipped. Corrected to 51.
The alias collision, which is a property of the corpus growing
build-corrections.mjs refused the build: two facts claim the api "engines": LF4 and LF9, and the same for peerDependencies, node version, react version.
The gate is right. also_known_as is a routing key — findings are joined to facts through it — and a string that resolves to two facts routes nothing. LF4's aliases were unambiguous while it was the only engine fact react-router had, and ambiguous the moment LF9 filed the same surface at a different major. Neither fact is wrong; the alias stopped identifying one of them.
Both sides were version-qualified, in the same commit, rather than stripping the alias from the newcomer: react-router 8 engines / react-router 7 engines, and so on. Nothing is lost from the lookup — the MCP server's check_api matches on substring, so a query for engines still reaches both, one rank below an exact name. No finding joined on any of the four strings, so nothing published moved. Expect this whenever a library files a second requirement, default-changed or deprecated fact about a surface it has already filed once.
Counts
react-router 8 → 9 facts; corpus 199 → 200. Runs, findings and charges unmoved at 158 / 165 / 158 across 8 libraries — bisecting facts charges nothing and no battery ran. fact-citation-sweep.mjs: 453 api joins, 8 allowlisted, 8 matched, 0 stale. All five generated surfaces green under --check; identifiers.mjs, mcp-server.mjs and the benchmark runner selftests green.
LF9 charges nobody today and is chargeable material tomorrow. 7.0.0 (2024-11-22) sits in Claude Haiku 4.5's live band — above its measured 6.28.0 boundary, below its 2025-02 cutoff — which is the same band LF8 sits in and the reason BACKLOG 1g-f-ii exists. It is now a two-fact window rather than a one-fact window, and the two ask different questions of a subject: what the package contains versus what it requires.
For the next session
- The 51-rung ladder is still installed at
%TEMP%\sp-rr-bisectand survived this session intact (verified byte-identical after the blinding test restored it).prisma.mjsis 1g-g's second adoption candidate and its ladder is not on disk, so that refactor waits for a session installing it — the same condition this one was waiting on, unchanged. - 1g-f-ii is the battery both 7.0.0 facts are pointing at and is unblocked.