067 — The notes were wrong about their own releases, five times out of twenty-seven

2026-09-06. No draws this session. BACKLOG 11k-t: build a bisect harness and turn it on data/zod/facts.json, the file whose base rate of misdating JOURNAL/066 measured at two in six.

Twenty-seven executable probes across fifteen installed zod releases, plus one settled with a TypeScript compiler. Eight of the twenty-three zod facts that carry an introduced_in were wrong — three in the version, five in what the change actually was. All eight are corrected and republished. Two of them are the facts under charged findings, and one of those corrections moves a published charge onto a release the subject demonstrably knows, which is a question this session deliberately did not answer.

The instrument

tools/audit/bisect-facts.mjs plus tools/audit/probes/<lib>.mjs. The probe file is the evidence and is committed; the installs are not. Each probe is a function of the module that returns true when the fact's own statement is true of that release. The runner walks the ladder, reports the first release where the probe turns true, and names the release below it — that adjacency is what makes the result a bisect rather than a spot-check. It also checks contiguity: once true, true all the way up. A gap means the probe is measuring something other than the feature, and the row is not evidence until the probe is fixed. That check earned its place twice in the first run.

The tool reports and never writes to facts.json. A correction is a decision.

Ladder: 4.0.0, 4.0.17, 4.1.0, 4.1.6, 4.1.13, 4.2.0, 4.2.1, 4.3.0, 4.3.1, 4.3.2, 4.3.6, 4.4.0, 4.4.3, 4.5.0, 4.5.4 — every minor boundary, the last patch of each minor line, and the specific patches earlier work had put in question.

What it found

Three wrong versions.

LF2 — .safeExtend() and the .extend() overwrite throw: 4.3.0 → 4.1.0. Two minors early. .safeExtend is undefined at 4.0.17 and present at 4.1.0, and the overwrite throws from 4.1.0 up. The 4.3.0 release note announces behaviour that had shipped four months earlier. The fact already carried a 4.1.0 citation with an empty quote — someone half-noticed and did not follow it.

LF17 — z.slugify(): 4.2.0 → 4.1.13. This is the second correction to this fact in one day and the first one was also wrong. Yesterday it moved 4.3.0 → 4.2.0 off a spot-check that tested 4.1.0 and 4.1.12 and stopped. slugify is absent at 4.1.12 (2025-10-06) and present at 4.1.13 (2025-11-24), three weeks before 4.2.0, so it is not a backport. The feature shipped in a patch, was still undocumented at 4.2.0, and was announced in the 4.3.0 notes thirty-seven days after it shipped — not the sixteen the fact claimed. A spot-check that samples the ends of a minor line cannot see a feature that lands in its thirteenth patch.

LF11 and LF13 — 4.4.0 → 4.0.0, and these are the strangest results in the run. The 4.4.0 release note says base64 validation "now rejects whitespace" and record schemas "now run transforms on record keys". Both behaviours are present in every stable 4.x release, 4.0.0 included — on z.base64(), z.string().base64() and z.base64url() for the first, and on the exact correct_code snippet for the second. The notes describe changes that are not observable anywhere in the released line. They are true corrections for someone holding a v3-era belief and they are not 4.4.0 migration items, and the facts now say so.

Five wrong mechanisms — the same version, a different thing happening.

LF4 was published as a runtime throw and is not one. "Object masking methods now validate that the keys you pass actually exist on the schema, and throw on an unrecognized key", severity S1. In fact .pick({ nope: true }) returns a schema, without error, on 4.0.17, 4.3.0, 4.3.6, 4.4.0 and 4.5.4. The check is a TypeScript type error: the pick() signature gains & Record<Exclude<keyof M, keyof Shape>, never> at 4.3.0, and under tsc 5 --strict a mask passed as a variable compiles at 4.2.1 and fails at 4.3.0 with TS2345. An inline object literal was already rejected before 4.3.0 by ordinary excess-property checking, so the release only bites the non-literal case. The version was right and the failure mode was wrong, which is the more dangerous error of the two: a JavaScript caller acting on our fact would go looking for a throw that never arrives.

This is also the first fact the runner cannot settle. It is now declared unprobeable with the reason and the tsc evidence attached, rather than quietly dropped.

LF12 — z.cuid() was tightened AND loosened, and the note says only "tightened". The pattern goes from /^[cC][^\s-]{8,}$/ to /^[cC][0-9a-z]{6,}$/ at 4.4.0. Uppercase and punctuation after the leading c are now rejected — but the minimum length drops from 9 characters to 7, so "cjld2cj" is rejected at 4.3.6 and accepted at 4.4.0. A reader who tightens their own validation on the strength of the note gets the direction half wrong. The first probe written for this fact used a well-formed CUID v1 and reported NEVER_TRUE, which is what sent us to read the shipped regex; the fact's own statement was too vague to probe, and that was the finding.

The deprecation half of the fact is documentary only: no shipped 4.x release rejects a well-formed v1 id and there is no @deprecated tag in the 4.4.0 or 4.5.4 typings.

LF21 — 4.4.0 is a repair, not an addition, and the history is not monotonic. z.union([]) constructs and fails at parse on 4.0.0; throws at construction from 4.0.17 through 4.3.6; constructs again from 4.4.0. The contiguity check caught this as a NON_CONTIGUOUS row and it turned out to be real library history rather than a broken probe — a regression in a 4.0.x patch that stood for eight months. The version stays; the note now says what actually happened.

LF31 — .deepPartial() is not a method. The fact wrote .deepPartial() / .exactPartial() as a pair. z.deepPartial(schema) is a top-level function; .exactPartial() is a method. A reader copying the leading dot gets undefined is not a function. Every other clause of the fact bisects clean to 4.5.0.

The charged findings underneath

Two published charges rest on LF2 and therefore carried the wrong release date. Both are re-dated to 4.1.0 (2025-08-23), and the two land differently:

What this says about where facts come from

Of twenty-three zod facts with a version, eight were wrong and every one of the eight was written from a release note. The fifteen that survived were written the same way, so the note is not useless — but the error rate on it is a third, and the errors run in both directions and in both kinds. HARNESS took Verify the release note against the artifact BEFORE writing the fact on 2026-09-06 (JOURNAL/057). This is the first measurement of what that rule is worth, and the answer is: it is worth a third of the file.

Two sharper things than the base rate:

A release note is a claim about a release, and it is subject to the same failure the Index studies. LF11 and LF13 announce changes the released code does not contain. That is not a model being stale; it is the primary source being wrong about its own artifact. Our whole method rests on primary sources outranking model recall, and this is the first hard evidence that the primary source needs a check of its own.

The version can be right while the fact is wrong. Five of the eight errors were mechanism, not date — a type error published as a runtime throw, a bidirectional change published as unidirectional, a repair published as an addition, a function published as a method. A bisect on introduced_in alone would have caught three. Writing a probe that has to execute the statement caught all eight, because a statement you cannot turn into a passing assertion is a statement you do not actually hold.

What was not done

Eight zod facts sit at 4.0.0 (LF5, LF22–LF28) with no probe. They are v3→v4 major-boundary facts and bisecting them means running the 3.x API, which is a different exercise with a different ladder. They are listed by the tool as unprobed rather than counted as confirmed.

Six libraries are untouched. On this file's base rate — a third wrong — there are roughly twenty more bad facts in the Index, and better-auth has thirteen facts of which every one carries a non-major introduced_in, which makes it the next pick.

Counts

154 runs, 164 findings (157 chargeable), 7 libraries — all unchanged; no draw ran. Eight facts corrected, two published findings re-dated, one charge flagged for adjudication, one new tool.