252 — The two artefacts that travel, and the date they did not carry
2026-09-25 · distribution lane · DISTRIBUTION D5, step 4's remaining half
What was open
Step 4 proposed that the generated artefacts print "the range of per-fact dates, or the newest". Half of it was refused on 2026-09-24 and the refusal stands: the newest is the flattering direction. A library file whose oldest fact was checked on 2026-09-02 does not become a 2026-09-24 artefact because one row moved. The single date an artefact states about verification must stay the floor — the file-level verified_on, the sweep date.
What was still open was the other half, and step 4 called it correctly: the correction pack and the rules file print one date per library and could print what they actually contain, computed from the artefact with no git at build time.
The gap was already closed everywhere a row travels
That is why it had gone unseen. substance_changed_on (JOURNAL/246, /248, /250) is the day a fact's own text last changed — a claim about the record, about no source. Every surface where the unit that travels is a row already ships both dates side by side: hf/'s three JSONL configs, by JOURNAL/198's rule, and the served data/<lib>/facts.json.
The two artefacts where the unit that travels is a file of prose did not. A correction pack pasted into somebody's CLAUDE.md; a rules file committed beside somebody's code. These are the artefacts this lane ships furthest — channel 1 and channel 3 of the mechanism — and each stated one date and disclosed no drift at all.
The number, measured before the wording was written
29 of 231 facts have had their text change since their own file's sweep, across 5 of the 8 libraries. It is not evenly spread, and the worst case is one of the biggest artefacts:
| library | sweep (verified_on) | facts | changed since | newest |
|---|---|---|---|---|
| next.js | 2026-09-02 | 28 | 13 | 2026-09-25 |
| better-auth | 2026-09-07 | 13 | 5 | 2026-09-23 |
| tailwindcss | 2026-09-06 | 34 | 4 | 2026-09-23 |
| valibot | 2026-09-11 | 19 | 3 | 2026-09-24 |
| zod | 2026-09-11 | 46 | 3 | 2026-09-22 |
| react-router | 2026-09-22 | 14 | 1 | 2026-09-23 |
| langchain | 2026-09-06 | 38 | 0 | — |
| prisma | 2026-09-12 | 39 | 0 | — |
Very nearly half of the next.js pack has been rewritten since the date it prints, the newest of those rewrites on the day this was written. A reader holding that file was told verified 2026-09-02 and was told nothing about the thirteen.
What the wording had to not say, which was the hard part
A text change is not a re-verification. JOURNAL/246 is the reason the field carries the name it does: of the first 29 substance changes, 17 moved a note and nothing else, off bisector results and severity re-scores — measurements against installed packages, which is exactly what this lane's own fact-provenance.mjs refuses to call a primary-source check. Rendering those as freshness would have put 29 claims nobody made into the two artefacts that travel furthest: the same failure the field's rename prevented, one surface along.
So the gate is on the adjacency rather than on the vocabulary. No sentence may carry the change date and any form of the word verified. The first spelling of that check looked at everything following the date anywhere in the rendered text, and it failed — on the disclaimer's own sentence, "the sweep date above is the only verification date this file carries", which is the one place the word belongs. Per-sentence now; and the zero arm is skipped rather than passed, because with no change date there is nothing to be adjacent to, and a check that is vacuously true in one arm reports clean having read nothing.
tools/lib/record-currency.mjs
Offline, pure, no git, no fs on the build path, ~0.1s. One rule, two renderings — a terse line for the header comment and a paragraph for the body — imported by build-corrections.mjs and build-rules.mjs so neither carries a copy (JOURNAL/211). It is a pre-commit link in its own right, for coverage.mjs's reason: the failure mode is silent in the flattering direction.
Decisions the build forced:
- A missing
substance_changed_onis a loud refusal naming--stamp, never a quiet zero. Counted as "unchanged since the sweep" it would lower the published drift count and print nothing. It can only fire on a treebuild-index.mjsalready rejects — which is precisely when a renderer should refuse rather than average over it. - The zero is printed. Three of eight libraries are at zero today, and
0 of 38and "this build does not compute it" are the same absence to a reader holding the file. D6's rule, one surface along. newestis read off the rewritten subset, never off the whole file. A file whose newest change predates its sweep has drifted by nothing, and printing that date after the word "since" invents a change that never happened. The selftest asserts the prose cannot name it.- Strictly greater. A change on the sweep day is covered by the sweep;
>=would report every fact touched during a sweep as having drifted from it. - The line names the sweep date rather than pointing at it. In the pack the block sits under
Latest x: v · verified <date>, where "that sweep" has an antecedent; in the rules file the same block sits under the licence notice, where it has none. Caught by reading the rendered rules file, not by the selftest — one self-contained spelling for both beats two that can drift apart. - The header comment is terser than the body, deliberately, and is not a second copy of the argument. An HTML comment is the part of a pasted pack a reader strips (terms.mjs's own observation), so the explanation lives in the body, where it survives.
- Two agreement bugs the selftest caught on its first run:
1 of the 1 entries, and a plural arm reading "13 entries have had its own text change" — the verb was taken from the count and the possessive was not.
Proof
- Selftest: every refusal driven until it fired, both boundary arms, both renderings, both blind-instrument directions. The real corpus is a positive control — every other check would pass against a renderer that computes zero, so it asserts the corpus actually contains the phenomenon (29 facts across 6 files) and fails if it ever stops.
- Differential, not eyeballing: a second implementation parsed the counts back out of all 24 rendered artefacts (8 packs, 8 rules files, 8 Cursor copies) and compared each to
facts.json— 0 mismatches. build-site.mjs: 8 pages changed, 0 new, 495 unchanged —page-content.mjsbehaving; the note is on the pack pages and nowhere else.- Nothing published moved that should not have. No fact edited, so no
--stampwas owed.data/index.jsonunchanged.checkOfferProvenancestill 27 of 27 — step 4's earlier finding standing, since the offer line carries the floor and the floor did not move.claims-surface.mjs6 occurrences / 6 accounted / 0 not.pack-test.mjsgreen (the server's imports are unchanged, so neither whitelist moved). Full chain green.
Named and not done
The site's per-library pages render the same verified_on in HTML and do not carry this note. They are a different artefact — the reader is on the site, one click from the JSON, rather than holding a file with no context — so whether they want it is a judgement rather than a batch, and it is a chunk of its own.
Procedural
Took the lock by hand before reading the charter's rule that it is taken with tools/session-lock.mjs; released and re-took it with the tool before doing any work. The hand-written file is the honoured pre-2026-09-22 format, so nothing was at risk, but the tool is the guard and reading the charter first is the protocol.