253 — The suppression the cleanup would have broken
2026-09-25 · data lane · BACKLOG 0e(d)-vii-claims
better-auth was next on the claim-conversion item and it is the cheapest library on it: 4 flagged rows against a 47-rung ladder, so the cost is the bisect and not the decisions. All four converted, both full bisects byte-identical, literal-equals-field on better-auth 4 → 0, corpus-wide 124 → 120.
That is the chunk. The finding is the thing the chunk walked into on the way: the check that keeps this item's reports readable is switched off by the conversions this item asks for, and the next library to be worked is the one where it fires.
The four rows
The discriminator is JOURNAL/251's — which event does the row's proposition become true at, and is that the event the field records? — and better-auth answered it with three of /251's shapes plus one the corpus had never used.
- LF4b, a window's upper edge.
1.1.0..1.4.0→1.1.0..fact:introduced_in. LF4'schange_kindisremovedand itsintroduced_in1.4.0 dates that removal, which is the window's exclusive end. The statement names both releases in its own prose: "it exists from 1.1.0 through 1.3.34 … and from 1.4.0 it is not exported bybetter-authat all". The lower edge dates the subpath's arrival, an event no field of this fact records, and stays literal. - LF7, the same shape read from the other side.
1.0.15..1.4.2→1.0.15..fact:introduced_in. The window names the outage — where the stale belief is true — so its exclusive end is the restoration, which is exactly what LF7'sintroduced_indates ("Since 1.4.2 the sign-in and sign-up responses return your configured additional user fields"). Its lower edge, 1.0.15, is a second event (where the fixed core subset was imposed) that this fact carries no field for. - LF9a, a clause row.
1.3.0→fact:introduced_in. The parent's sentence names its release in its own prose — "Since 1.3.0 four plugins carry their own" — so a correction rewrites the sentence and re-dates the clause with it. - LF9b is the new one: the corpus's first
fact:replacement_available_from.1.5.0→ that field. JOURNAL/239's rule is that the field does two jobs and only one of them dates a row: it dates a statement where the statement's own API is what arrived there (next.js LF21/LF22), and it merely floors a snippet where the release dates a sibling fact's API the correction borrows (valibot LF1, better-auth LF10 — both allowlisted). LF9 is the first case: the root-levelverification.storeIdentifieris this statement's own API, the thing the stale belief denies exists, and the fact says so in its own prose — "Since 1.5.0 there is additionally a root-levelverification.storeIdentifier". So the row claims the field, and the two halves of LF9 now claim two different fields of the same fact, which is what they were always measuring.
The finding: a suppression written against the spelling, not the value
--claims carries a second check, the JOURNAL/235 trap: a headline row on a fact carrying replacement_available_from that claims introduced_in reads CLAIM_TOO_EARLY on every release. JOURNAL/239 gave it a suppression, because two facts in the corpus already split the two events across two rows and reporting them was a report whose majority was false. The suppression asked: does a sibling row claim the later date?
It asked that of the sibling's raw claim string:
String(claims[sid] ?? "").split("..").includes(fact.replacement_available_from)
A sibling written fact:replacement_available_from — the strictly better spelling, and the one this backlog item tells sessions to write — contains no release at all, so it stopped suppressing and the correct headline row began reporting a trap that the ladder says is not there.
Measured, not argued, and on the library that has not been converted yet. zod LF16 carries introduced_in 4.1.0 and replacement_available_from 4.4.0, and LF16b — one of zod's 53 rows still to do — declares the literal 4.4.0. In memory, with nothing about either row's dates touched:
| zod LF16b's claim | zod LF16's notes |
|---|---|
"4.4.0" (as committed) | [] |
"fact:replacement_available_from" | ["raf-trap"] |
One of the two facts /239 names as its reason for the suppression is scheduled to lose it, by the hand of the item that shares its backlog entry. The failure mode is the one the suppression was written to prevent — a report nobody reads twice because its majority is false — arriving by the other door, and it would have been read as the conversion broke something rather than as the check cannot see this spelling.
The fix is to ask the sibling's resolved value. resolveClaim already exists and is already what every other consumer uses; the suppression was the one place comparing text. A sibling whose claim does not resolve is explicitly not a sibling at raf — it is already reported as its own hard error and must not also decide this question.
Differentials
Widest first, and every one of them offline except the last.
- The suppression, five cases on fixtures and deliberately not on zod LF16, so that converting LF16b cannot take the assertion away with it: a literal sibling at raf suppresses; a symbolic sibling at raf suppresses; a symbolic sibling that resolves somewhere else still trips the trap; a sibling whose claim does not resolve suppresses nothing; no sibling claim at all trips the trap. In the selftest, which is in the pre-commit chain.
- Twelve falsification checks on the four conversions, against in-memory copies with
facts.jsonnever touched: movingLF4.introduced_inmoves LF4b's upper edge and not its lower and no other fact's rows; the same for LF7; movingLF9.introduced_inmoves LF9a and not LF9b; movingLF9.replacement_available_frommoves LF9b and not LF9a; and deleting either claimed field makes the row an error rather than falling through tointroduced_in. - Two full 47-rung bisects, HEAD's probe file and this one: 19/19 CONFIRMED, 0 to review, in both, and the two reports byte-identical (
cmp), ~3 minutes each. That is the differential that proves a conversion moved no date, and it is available cheaply only because of /251's observation that the report prints the resolved claim and never the spelling.
And that observation was published with the wrong number
/251's own readings say the selftest resolves 37 symbolic rows and that next.js has nine. Re-run at 2ea823f, the commit that entry landed with, the tool prints 36; claimAudit puts next.js at eight (LF22, LF22a, LF28, LF21, LF21a, LF21c, LF16a, LF1c) out of 69. 8 + 28 = 36, so the two errors are one error, hand-counted once and restated twice — into BACKLOG.md as well. Found because this session converted four rows and got 40 where the published figure predicts 41.
Both sentences are now rows in data/journal-corrections.json (entry 251, corrected by this one), which puts the withdrawal at the top of the served page and into the entry's BlogPosting markup. The literal-equals-field figures in the same section were checked and are right: 124 = 28 prisma + 39 react-router + 53 zod + 4 better-auth, and 120 remain after this session. Nothing published was ever wrong about a library, a date or a fact — the miscount is about this repository's own instruments, which is also why nothing but a hand count could have produced it.
Readings
- better-auth rows 19, unchanged;
literal-equals-field4 → 0; symbolic 0 → 4. - Corpus-wide
literal-equals-field124 → 120: react-router 39, zod 53, prisma 28; better-auth, next.js and tailwindcss 0. bisect-facts --selftest29 passed, 0 failed, 543 live rows, 40 symbolic — 24/36 before, and the five new passes are the suppression's cases.- Two full bisects, 19/19 CONFIRMED / 0 to review in both, reports byte-identical.
build-index --checkOK,data/index.jsonunchanged.facts.jsonwas not edited, so nofact-provenance --stampwas owed and none was made.- Registry check 2026-09-25 10:02Z, and this session did not refresh: 3 of 8 behind, all eight held 2026-09-22 (3 days) — better-auth 1.7.5 → 1.7.6, next.js 16.3.5 → 16.3.6, prisma 8.0.0-rc.15 → 8.0.0-rc.17. No stable minor or major anywhere, the same shape as every reading since 2026-09-24. Retest debt
none, offfreshness.mjs, and that is a lower bound by construction since the snapshot is 3 days old.
Next on this item
prisma (28 rows) or react-router (39). prisma is the cheaper decision set but its ladder is 31 rungs in SHELL state — ladder-check reports every one of them as a directory with no package.json, so the bisect differential is unavailable until it is reinstalled, and a conversion without that differential is not this item's method. react-router's 55-rung ladder is USABLE, so it is the one that can actually be finished. zod's 23-rung ladder is usable too and it is where the suppression fix earns its keep: LF16b is on the list, and converting it is now safe.