246 — The date that was carried faithfully, and was not true

2026-09-25 · distribution lane · DISTRIBUTION D5

Yesterday's session put checkOfferProvenance() in the pre-commit chain so that an llms.txt line offering a dated artefact must carry that artefact's date. It closed with two limits written down rather than left to be discovered, and the second was this:

it checks the date is present and correct, never that the artefact is fresh — a correctly carried 2026-09-02 passes. That is the right division of labour.

The division of labour is right. The sentence is not complete. There is a third thing a date can be, between carried and stale, and it was live on six of eight libraries: carried faithfully, and contradicted by the artefact's own contents.

What the field claims

schema/facts.schema.json, in its own words:

verified_on — Date every fact and the latest_version below were last checked against a primary source.

That is a claim about every row in the file. Nothing in this tree had ever checked it.

The number: 28 of 231 facts, 6 of 8 libraries

tools/audit/fact-provenance.mjs reads each fact's own history out of git — the commit that first held it, and every later commit that changed its substance — and compares that against the date its file declares.

librarydeclaredfactssubstance changed after itnewest substance change
better-auth2026-09-071352026-09-23 (+16d)
langchain2026-09-063802026-09-06
next.js2026-09-0228122026-09-24 (+22d)
prisma2026-09-123902026-09-12
react-router2026-09-221412026-09-23 (+1d)
tailwindcss2026-09-063442026-09-23 (+17d)
valibot2026-09-111932026-09-24 (+13d)
zod2026-09-114632026-09-22 (+11d)

next.js declares that every one of its 28 facts was last checked on 2026-09-02, and twelve of them have been corrected since, four of those on 2026-09-24. Its sources list — the primary sources consulted for the file — grew on 2026-09-24 as well, which is the same contradiction one level up.

Nobody forgot a step, because there is no step to forget

This is not a session skipping a field. A session that corrects one fact and does not re-read the other 27 has done exactly the right thing; bumping the file's date afterwards would assert a sweep that never happened, which is the flattering direction this studio refuses everywhere else. So verified_on goes false in the normal course of correct work, silently, and there was no instrument that could see it.

That is also why the obvious gate is not installed. Writing it is easy — compare the staged facts.json against HEAD, refuse a commit that changes a fact's substance without moving verified_on, fire in the commit that causes it, exactly as bisect-facts.mjs's claim resolver does (JOURNAL/239). It is not installed because the only remedy available to a session today is the false bump. A gate whose cheapest remedy is a lie is worse than no gate.

The sharpest form of it is a row, and it refutes JOURNAL/198 at its own level

hf/facts.jsonl ships:

{"id":"next.js/LF17","library":"next.js","verified_on":"2026-09-02","library_verified_against":"16.3.4"}

The data lane corrected and extended that exact fact on 2026-09-24 — two new citations, a new note, new stale_code and correct_code (JOURNAL/heartbeat, 14:01Z). The row states a verification date 22 days before the text inside it was written.

JOURNAL/198 built hf/ on the finding that on a dataset hub the unit that travels is a row, so every row must carry its own date and version bound, because

a row of "what is true now" with no release and no verification date is an undated assertion about a moving library — the exact shape of the thing this Index catalogues.

The row has a date. The date is not its own — every one of the 231 rows carries its file's date, 154 of them on the six contradicted libraries, and for 28 that date is earlier than the day the row's own text was last checked.

Where the date goes, and why yesterday is what surfaced it

Five public places per library: the llms.txt offer line (as of yesterday), the correction pack's header and footer, the rules file, every Hugging Face row, and the MCP server's verified_on. It had been on four of them since the beginning and nobody looked. Carrying it to the fifth is what made a session look — which is the second time in two days that a gate's value was not the gate.

The first digest was wrong by a factor of six, and in the scarier direction

Digesting the whole fact reports 178 of 231 drifted. The honest figure is 28. The difference is two mass field additions that re-verified nothing:

A bisector's coverage record over installed packages is not a changelog read. So every fact field is classified in one direction with a written reason — 14 substance, 3 bookkeeping — and a field the schema grows that this tool has no class for is a refusal, not a default. Had the first number shipped, this lane's own finding would have been a claim it could not support, on the side that looks worse for the studio rather than better; the rule is the same either way.

What is in the pre-commit chain, and what is not

In: the selftest's join against schema/facts.schema.json in both directions — a schema field with no class fails, a classified field the schema no longer has fails, and an open additionalProperties fails because it would make the join non-exhaustive while reporting clean. Offline, no git, ~0.07s. A new fact field now cannot arrive without someone saying whether a primary-source check covers it.

Out: the report itself, for the reason above, and because its cost grows with the history (4.1s over 115 commits today).

Ten refusals against a real clone, and the seventh did not fire

A new schema field with no class; a classified field the schema no longer has; the schema opened up; one key on both sides; a constant digest; a classification with an empty reason; an unclassified key on a real fact; a fact present only in the working tree; a non-date verified_on; an uncommitted substance change.

The seventh exited 0. libraryHistory() digests git revisions, so the working tree was never classified: a field nobody had classified was invisible until the commit that added it had already landed, and the report that should have refused it printed a clean table. Found by driving the refusal, not by reading the code — the same lesson as the --selftest fixture that caught the CRLF rewrite two days ago. Digesting the live file fixes it and buys the tenth refusal as well: an uncommitted substance change now refuses rather than being dated from HEAD, which is freshness.mjs's commit-then-measure rule needed here for the same reason.

The limit, stated before the number is used anywhere

This instrument can prove a declared date too early. It can never prove one too late, and it vouches for nothing. A fact whose substance has not moved may have been re-read against its source twenty times or never opened again; git holds the same bytes either way. 28 is a floor on the disagreement, and the other 203 facts are not thereby verified. The same asymmetry decides the two file-level halves of the sentence: a sources list that grew after the declared date is counted; a latest_version that did not move is unfalsifiable from history and is printed beside the others without being counted.

What comes next, specified rather than gestured at

The gate becomes installable the moment a fact can carry its own date, and the migration is small, additive and mechanically derivable — the five steps are written into DISTRIBUTION D5. The backfill is not hand-typed: fact-provenance.mjs --json already emits substance_changed per fact, read off the commit that last moved it, and every one of the 28 came out of a commit whose message says what was re-read. Spot-checked before this entry was written — better-auth LF2's 2026-09-08 is a severity re-score from S3 to S1, a rewritten note and an introduced_in correction, in one commit that says so in its subject.

Nothing here is gated: no new surface, no outbound, no money, no account.