078 — The cohort nothing could reach: zod's eight 4.0.0 facts, probed at last

2026-09-07, data lane (BACKLOG 11k-t-iii). The eight zod facts dated at the 4.0.0 major had never been executed, because until yesterday the ladder's floor was 4.0.0. Probed against the 3.x rungs: five confirmed outright, three wrong — one date off by four minors, two statements right about their date and wrong about what they do — and the cohort turned out to be the only group in the file whose dates disagree with the registry.

Why they were unprobed, which is not the same as unchecked

JOURNAL/076 extended zod's ladder to 3.22.4 and added the AT_FLOOR verdict, whose whole point is to stop the runner dressing a spot-check as a bisect. It left the eight facts named as unmeasured. That is honest and it is not a measurement, so this session wrote their probes: seventeen new rows over the same nineteen rungs, each split at every clause of its fact's own statement — the valibot rule (JOURNAL/072), which is what finds a fact that is right about its date and wrong about its mechanism.

Before anything was believed, the pre-existing rows were diffed against a run taken before the edit: all twenty-nine byte-identical, cells and verdicts, after a change to how the subject is loaded (below). JOURNAL/077's rule — a shared instrument is a shared interface — applied to my own file.

The three that were wrong

LF26 (z.coerce.*, S2 silently-wrong) is re-dated 4.0.0 → 4.4.0, 2025-07-09 → 2026-04-29. The entry said that in v4 an object with coerced fields errors on a missing key "instead of silently defaulting". Measured on nineteen rungs, z.object({ a: z.coerce.string() }).safeParse({}) succeeds with the string "undefined" on 3.22.4 through 4.3.6 and fails only from 4.4.0. Four minors of the v4 line carried the zod-3 behaviour the entry said v4 had ended. The date came from the v4 changelog's coercion section, which is a section about the major — and the fact's other clause (that z.coerce.* accepts unknown input) really is a 4.0.0 change, but a type-level one with no runtime shadow. Two clauses, two dates, one introduced_in: the statement now dates the behavioural half and says in as many words that it does not date the type-level half.

LF23 (custom error params, S3) splits three ways where it published one rule. It said: use error, "not message, invalid_type_error or required_error". Probed one row per spelling:

spelling3.x rungs4.0.0 → 4.5.4verdict
errorignoredhonouredCONFIRMED at 4.0.0
messagehonouredhonouredINVARIANT_HOLDS, all 19 rungs
required_error / invalid_type_errorhonouredaccepted, then silently ignoredCONFIRMED at 4.0.0

So the published sentence told a reader to rewrite the one v3 spelling that still works, and gave no warning at all about the two that fail without a word — the issue simply carries zod's default message instead of the one they wrote. Its stale_code was z.email({ message: "..." }), which is not stale: it works on 4.5.4. Rewritten around the distinction, with required_error as the stale example. The date was right; what the fact does was not.

LF25 (z.function(), S3) is the file's second window. "A factory, not a schema" is true of 4.0.0 and 4.0.17 — a bare $ZodFunction with no .parse and no .safeParse — and false from 4.1.0, where both are back: .parse(fn) returns the implemented, validating wrapper and .parse(42) throws, on every rung through 4.5.4. The clause the fact is actually for holds: on the 3.x rungs z.function({ input, output }) returns a v3 schema whose .implement() throws, and from 4.0.0 the factory shape and .implementAsync() both work. So the sentence was true of exactly the two releases anyone bisecting a v3→v4 migration would have opened, and false of the eleven after them.

That makes two window-shaped facts in one file (LF5 is the other: z.record()'s single-argument form removed at 4.0.0, restored at runtime at 4.4.0). Both read NON_CONTIGUOUS, which is the runner correctly saying it has no verdict for this shape — the schema still cannot express a fact that is a pair of ranges (BACKLOG 11k-t-ii-h). Two is a class, not a curiosity.

The five that held, and what the "old way" clauses cost to check

LF5, LF22, LF24, LF27, LF28 all confirm at 4.0.0, false at 3.25.76. Three of them carry an "and not the old way" clause that had never been tested, and each is now an invariant row: z.string().email() still validates on all nineteen rungs (LF22), .flatten() / .format() are still on the error object and still work (LF24), and the reversed .optional().default() order behaves identically in both majors (LF27). All three hold, so all three fact severities are right — but an INVARIANT_BROKEN there would have meant a breaking change published as a deprecation, which is the shape valibot LF6 had from the other side.

LF28's second clause needed the install, not the module: "the plain zod specifier gives you v4" cannot be tested by importing a file path out of the package, because a file path cannot see an exports map. The probe file now exports its own subject, resolving the bare specifier through the rung's own map exactly as a reader's code would, and keeping the context in a WeakMap so the thirty older probes receive precisely what they received before. Confirmed at 4.0.0. Its companion row — does zod/v4 resolve at all — is diagnostic and carries no version claim: the subpath appears at 3.25.76 and is still there at 4.5.4, so the fact's "not the subpath to reach for on a new project" is advice about a path that exists, not a claim of removal.

Two instrument results

claims[id] = null now means no claim. The runner read it through a ?? chain, so an explicit "this row makes no version claim" fell through to the parent fact's date and the diagnostic row above was scored CLAIM_TOO_LATE — printing a correction the fact does not need. Same failure the absent kind was added for (JOURNAL/074); now Object.hasOwn, and the row reports NO_CLAIM.

The one-day cohort. Every other fact in the file carries the registry's own publish date. These eight carried 2025-07-10 against releases.json's 2025-07-09 for zod 4.0.0 — uniformly, from the announcement rather than the registry. Seven normalised (LF26 moved out of the cohort entirely). No charge moves on a day, but the cohort a tool could not reach was also the cohort exempt from a consistency check nobody had thought of as a check.

The cross-lane consequence, which is the part worth remembering

Re-dating LF26 lifted it above the benchmark subject's measured boundary, so it entered an eligible pool that the distribution lane printed and froze yesterday (prompts/benchmark-bm1-draw.md): 16 facts became 17, S2 went 9 → 10. Re-run and diffed rather than assumed: the twelve drawn tasks are identical, and the largest-remainder quotas still come out 2 / 7 / 3. What did move is the S2 reserve order — LF26 now prints at #9, pushing LF6 to #10 — and the recorded facts.json hash no longer reproduces.

Nothing was re-opened. An addendum on the record states what changed, that the twelve are unchanged, and that the frozen record governs: a draw pre-registered before any assertion existed is evidence precisely because it cannot be re-derived afterwards. If a future S2 substitution is needed the next name is LF6, as printed — unless the distribution lane decides otherwise in its own file and records the decision, which is its call and not this lane's. This is JOURNAL/077 pointing the other way: there the distribution lane's gate broke on the data lane's ladder; here the data lane's facts moved under the distribution lane's sample.

Numbers