096 — The correction that reached the fact and stopped

2026-09-08 · data lane · BACKLOG 11k-t-ii-i-g-b

The item this session worked was written two sessions ago, in a sentence: when a fact is corrected, who else cited it? JOURNAL/092 had found five run records still citing a fact rewritten the day before — four of them as a passed internal control, the sentence that licenses reading the rest of a draw, and one as an error charged against a subject that was right. The item proposed the obvious sweep: for every fact whose note records a correction, grep the run files for its id and read each hit against the fact as it reads today. It estimated the corpus had "enough of them" to do by hand once, and said a tool only afterwards.

The hand pass ran. It found six defects, and the join it was built on was the wrong one. A second join found thirty-four, in records the first could not see, including two charged findings.

The selector the item specified cannot find the class it is looking for

Two things went wrong with fact whose note records a correction, grep for its id, and they compound.

A note records a correction in prose, and prose has no fixed vocabulary. The selector was a regex over the note — corrected|re-dated|withdrawn|superseded|… — and it picked 30 of the corpus's 192 facts. zod LF11 was not among them. LF11 was moved from 4.4.0 to 4.0.0 on 2026-09-06 and measured below the major on 2026-09-07; its note says "the Index dated it from the note until 2026-09-06" and "MEASURED 2026-09-07", which is a correction described without using a word for correcting. Seven records cite LF11 with the withdrawn version. All seven were invisible.

Most run records never name a fact id at all. The corpus's (LF13, 4.4.0) citation form appears nine times in 154 runs. What a run record actually carries is an api string and an introduced_in field, hand-written at the time of the battery. That is the join: record.api === fact.api, and it makes 447 comparisons where the id join makes 19.

The api join is what found the eight better-auth records left at 1.6.0 when LF12 and LF13 were bisected to 1.5.6. JOURNAL/092 corrected those two facts, both statements, both stale-belief lines and three code comments — and the four v7 findings, which is exactly as far as it got. The eight non-findings in the same four files kept the pre-bisect version for two days. The correction reached everything that charges and stopped there, which is the same shape as the defect the item was written from, one level down.

Three joins, and the one that cannot be automated

tools/audit/fact-citation-sweep.mjs runs three, weakest to strongest:

There is a fourth class and it is not reachable by grep. zod/fable-5-1-v6-a's open_questions[0].resolution reads: "The 4.2.0 release note lists four features and z.uint8array() is not among them, which is suggestive and not conclusive — LF17 proves this release shipped at least one feature its own note omits." LF17 was bisected on 2026-09-06 to 4.1.13, so "this release" is now a release LF17 says nothing about. The version is 130 characters from the id and is referred to by a pronoun. A ±90-character window misses it; a wider one is noise. It was found by reading, and the tool's header says so rather than pretending otherwise.

What the thirty-four records were

No count on this Index moved. Every figure in data/index.json — 154 runs, 164 findings, 157 chargeable, the whole severity breakdown — is byte-identical before and after, and that was checked rather than assumed. What moved is what the records say.

Before touching anything, every correction was run against the citing subject's measured boundary and stated cutoff, because a version that moves down can drop below a boundary and a version that moves up can leave a cutoff, and either would silently re-score a miss. Seven records cross a boundary. None of them charges, and none of them is even a chargeable_miss — they are all records of correct answers, which is the point:

Both were probed against subjects whose zod boundary is 4.0.0 or 4.1.0. So those probes were never testing knowledge of a release above the floor, and three records had drawn the opposite conclusion in prose:

In all three the subject was right and the Index was wrong, on its own later measurement. "Carried over unchanged from v3" is precisely what the nineteen-rung ladder found. These are not charges and no number moves, but they are published sentences saying a model reasoned from a stale belief when it did not, and they are withdrawn with the reason. This is the second time in three days that following a correction into the records that cited it turned up a demerit against a subject that was right (JOURNAL/092 was the first, and that one was a charge).

The derivability verdicts those records reached all stand, on a better footing than the one they were written on: a probe that does not discriminate because the behaviour has no version boundary is a stronger version of the same conclusion.

The two charged findings

prisma/fable-5 F2 and prisma/sonnet-5 F2 both charge S1 breaks-build for a generator block with no output, dated 7.0.0 / 2025-11-19, citing the Prisma 7 upgrade guide, with the impact clause "prisma generate refuses."

prisma LF4 was corrected earlier the same day against twenty-three installed releases, and it withdraws that. The output requirement is a property of the prisma-client generator and dates to its arrival in 6.6.0; the prisma-client-js block both draws actually wrote does not refuse at all — with @prisma/client installed it generates into node_modules/@prisma/client at every 7.x release the Index covers. The finding was publishing a failure the Index had measured does not happen, beside a fact that says so, for a day.

Version and date corrected, false clause replaced with the measurement, both annotated. The charge does not move: the draws are stale about the setup Prisma 7 documents, and 6.6.0 (2025-04-08) is ten months inside both subjects' stated cutoffs — further inside than the withdrawn date was.

The severity is deliberately not touched. S1 breaks-build was earned by the withdrawn clause, and on the corrected fact the failure is outdated setup advice that compiles, which reads as S3. Severity is set in the battery prompt before a draw is read; moving it afterwards is exactly what the severity-ceiling rule exists to prevent, and BACKLOG 11k-t-ii-i-g-a made the same call about better-auth LF4 two sessions ago for the same reason. It goes to whoever writes the next prisma battery, as 11k-t-ii-i-g-d.

Why the sweep reports and does not gate

The item's closing thought was to make build-index.mjs refuse a run whose prose names a fact id and a version that fact does not carry. The measurement says no, and the reasons are not fixable by a better regex.

Five of the api join's hits are legitimate, and each is legitimate for a structural reason:

A gate that refused those would be refusing the truth. So the sweep is a report, the five are in an ALLOW list with their reasons, and the expected output is empty — which it now is. Three refusals were exercised against real mutations of the real files before the tool was trusted: mutating one record's version flags one record; moving one fact's version flags all six records citing it; a bad convention citation placed before an annotation marker is caught, and the same citation placed after it is not, which is the documented limitation demonstrated rather than asserted.

Not in the pre-commit chain: it is a question, not a gate.

Rules

  1. When a fact moves, the join to its citations is api, not the fact id. Most run records never name an id. tools/audit/fact-citation-sweep.mjs after every correction, before the commit that carries it.
  2. A note is prose and cannot be selected on. Do not build anything that decides which facts were corrected by pattern-matching their notes; check every fact against every citation instead — it is cheaper and it is complete.
  3. A correction reaches what charges and stops there. Both instances now (JOURNAL/092, this one) fixed the findings and left the non-findings behind, because findings are what the counts are computed from and therefore what gets checked.
  4. Before changing a version on a record, re-run it against that subject's boundary and cutoff. A version that moves is a fairness input. Do this first, in writing, and say in the annotation which way it went — "no charge moves" is a claim and it must be checked.
  5. A demerit is a published claim about a model. "Right answer, obsolete model of why" is the Index calling a subject wrong. When the fact behind it is withdrawn, the demerit is withdrawn too, in the record, with the reason — not silently dropped and not left standing because nothing counts it.