040 — The release note that charged a model for a correct answer
2026-09-02. Backlog item 2f-ii, the last two unaudited npm packs: valibot (11 TS facts of 12) and better-auth (7). JOURNAL/039 had reclassified both from "cheap and low-yield formalities" to worth running, on the grounds that every pack in the repo had been audited by an instrument that could not see config fragments and these two had never been machine-audited at all.
valibot carried one defect. It was not the kind the tool was built to find, and it had already cost a model a finding it did not deserve.
isbn is announced in 1.2.0 and is not in the package
LF3 says the isbn validation action arrived in valibot 1.2.0, cites the v1.2.0 release notes, and prescribes v.pipe(v.string(), v.isbn()). Audited at its own introduced_in against an installed valibot 1.2.0:
tsc TS2339: Property 'isbn' does not exist on type 'typeof import(".../valibot/dist/index")'
The string isbn does not appear anywhere in valibot 1.2.0's shipped package — not in index.mjs, index.cjs, index.d.mts, index.d.cts, or either minified bundle. Zero occurrences across six files. At 1.3.0 all six carry it.
The release note is not a misquote. It is there, verbatim:
Add
isbnvalidation action to validate ISBN-10 and ISBN-13 strings (pull request #1097)
And PR #1097, feat: ISBN validation, really did merge on 2025-11-23 — one day before the v1.2.0 tag. The tagged source shows exactly what happened next. At tag v1.2.0, library/src/actions/isbn/isbn.ts exists. library/src/actions/index.ts at the same tag runs:
export from './ipv6/index.ts'; export from './isoDate/index.ts';
The alphabetical slot for isbn, between the two, is empty. The module was merged and never wired into the public entry point, so the bundler dropped it. The line export * from './isbn/index.ts'; first appears at tag v1.3.0, and the shipped export list moves from isOfType, isValiError, isoDate to isOfType, isValiError, isbn, isoDate.
And the v1.3.0 release notes never mention isbn at all. Neither release's notes are true about which release made the API usable. One announces an action its own package does not contain; the other ships it in silence.
This is the second time the Index has caught a release note that is wrong about its own release (JOURNAL/027 is the first) and the first time the auditor caught it rather than a human reading carefully. The standing rule — verify current behaviour against the shipped artifact, not the release notes — was written for exactly this and had never been applied to a fact's introduced_in date, only to its behaviour.
The part that is about us
introduced_in is not a decoration. The fairness rule reads it: a chargeable finding must name a release that precedes the subject's stated cutoff. Moving LF3 from 1.2.0 (2025-11-24) to 1.3.0 (2026-03-17) moves it across one of them.
valibot--claude-fable-5--v1--2026-09-01 F2, an S3, charged Claude Fable 5 for this:
"Valibot does not ship an
isbn()action (it hasimei,creditCard,mac, etc., but no ISBN), so this is a customcheck."
Fable 5's stated cutoff is 2026-01. isbn became usable in 2026-03. The subject denied the existence of an action that did not exist in any valibot release it could have seen, and did so while writing correct ISBN-10 and ISBN-13 checksum arithmetic. The original charge note read "1.2.0 published 2025-11-24, two months inside the subject's stated 2026-01 cutoff." Every word of that was true about the release note and false about the package.
F2 is withdrawn, into that run's non-findings as a correct answer. Not reclassified as a non-chargeable miss — that column is for reproduced failures the scoring rules bar from counting, and this was not a failure. The Index was wrong and the model was right.
The findings are not renumbered. F1 and F3 keep their ids; the gap is explained where F2 stood. Renumbering would break every published URL and pointer into the run to hide an error we should be advertising.
The Opus 5 charge on the same surface survives: that subject's stated cutoff is 2026-05, still two months above 1.3.0. It is re-dated, re-cited against the v1.3.0 barrel and the 1.3.0 tarball, and now carries the reason its sibling did not survive. Dataset totals: 125 findings → 124 (117 chargeable), across 72 runs.
better-auth: clean, after the tool stopped lying about two of its facts
Seven facts, four with code a type-checker can reach, all four verified at their own introduced_in — bearer({ requireSignature }) at 1.1.0, deviceAuthorization() and lastLoginMethod() at 1.3.8, database-less betterAuth({ ... }) at 1.4.0. LF4 has no correct_code. Zero defects in the data.
LF3 and LF7 first reported ok. They should not have. Both are claims about the shape of the object that comes back from authClient.signIn.email({ email, password }) — the reader's own client, which the type-checker sees as any. Nothing in either snippet is checkable. What produced the green was this: password was flagged as an unresolvable free name, and email was silently auto-imported from better-auth, which exports something by that name. Two shorthand properties of one object literal, treated differently because one of them collided with an unrelated export, and the snippet compiled against a meaning it does not have.
Declaring email in --placeholders did not stop it. The placeholder check only ran in the branch where resolution had already failed, so the operator's explicit judgement lost to the tool's guess. Fixed, with the two lists split: a --placeholders name now short-circuits the free-name pass entirely, and says so — "declared; better-auth exports the same name and was NOT used". The baked-in generic list (user, schema, config, …) stays after resolution, because it is a convenience with no pack behind it and a pack that genuinely exports schema must still be checked against it.
With the fix, LF3 and LF7 read UNCH — NOTHING RESOLVED, and better-auth's honest coverage is four of seven rather than six of seven.
The sharpest detail: at better-auth 1.1.0, which exports no email, LF3 was already UNCH. The verdict on that fact moved between ok and UNCH according to whether an unrelated release had added an unrelated export. A verdict that moves with something the fact does not mention is not a verdict. JOURNAL/039's rule — read the counters, not the verdicts — held again, one session later, in a shape nobody predicted.
Both changes were proved able to fail before either pass was believed: a misspelt valibot action (toKebabCasing) and a plausible near-miss (isbn13) both produced TS2551 with the right suggestion; a misspelt better-auth option key produced TS2561 against BearerOptions and a poisoned plugin import produced TS2724. valibot was then re-audited end to end with the patched tool and reproduced its results identically.
An environment note that will cost the next session an hour
npm install typescript in a fresh workspace now installs 7.0.2, the native port. Its . export is lib/version.cjs. There is no ts.createProgram, no ts.ModuleResolutionKind, no JS compiler API at all, and node-api-audit.mjs dies on its own module table:
TypeError: Cannot read properties of undefined (reading 'NodeNext')
It fails loudly, which is the good case. But every workspace recipe in JOURNAL/038 and /039 predates the 7.0 release and will not reproduce as written. Audit workspaces pin typescript@^5. Written into HARNESS.md beside the rest.
Where the audit queue stands
Six of seven packs are now machine-audited. What remains is about coverage rather than suspicion:
- prisma and zod re-run with
--fragment-type— both were audited before the flag existed. Neither valibot nor better-auth contains a labelled-statement config fragment, so the flag was not needed here and the question is still open for those two. - langchain cannot use
--fragment-typeat all; it is a Python pack and the flag is a TypeScript wrap. The backlog item that queued it was wrong about that. - tailwindcss — 1 TS fact of 29. The pack is CSS and HTML and this tool cannot read either.
- Nothing checks
prisma,bash,jsoncorcsssnippets, which is 14 of prisma's 25 facts.
Money
No money moved. Costs remain $0; the only input is Sam's subscription tokens. Eleven npm workspaces were created in the session scratch directory and are not committed.