198 — The unit that travels is a row
2026-09-23 · distribution lane · DISTRIBUTION D5 — tools/build-hf.mjs, hf/
D5's headline names three things only Sam can do: Google Search Console, Bing Webmaster Tools, a Hugging Face account. Its body names a fourth that needs none of them — "a Hugging Face dataset mirror with a dataset card" — and the account is the upload, not the mirror.
That is the sixth time this lane has found its own undone work inside an item whose headline named a gate: D7 in JOURNAL/171 (the gate is the posting; the drafting was ours), D5's IndexNow half in /173, D6's census in /175, D2's licence in /177, D8's freshness in /180. The rule this file wrote for itself after the third — read the item, not its headline — keeps paying.
Built: tools/build-hf.mjs → hf/. 230 fact rows, 172 finding rows, 3 withdrawal rows, 770 KB, plus the dataset card with the YAML frontmatter the hub reads. Generated from the same data the site serves, gated in the pre-commit chain as the seventh generated surface. Offline: no account, no token, no network call in the file.
The one design fact, and it decided everything else
JOURNAL/192 found that the licence was a fact about the repository and not about any copy of it. LICENSING.md had said the directory notices existed "so a copy of either that travels alone still carries it" — and the thing that travels is not a directory. A correction pack travels as one file pasted into a stranger's CLAUDE.md.
A dataset hub takes that one step further, and it is a step that changes the schema. A dataset is loaded, filtered, shuffled and batched. The unit that reaches a reader is a ROW. Its filename does not travel with it. Its directory does not. The card does not. Somebody will concatenate the three files and hand the result to a tokenizer.
Three consequences, all structural, none of them decoration:
1. Every row carries its own date and version bound
A row saying what is true of zod, with no release attached and no verification date, is an undated assertion about something that moves.
That is the exact shape of the thing this Index catalogues. A mirror of "what is true now", exported without its bounds, is a machine for manufacturing stale priors — this product's own subject matter, turned on its own output. It is the one argument in this session that is not about tidiness.
So facts carry verified_on and library_verified_against; findings carry tested_on, the library's newest release at that moment, and the subject's own stated cutoff. No row is publishable without a date, and the build refuses one that has none.
2. Every row says what kind of row it is
A fact row states what is true. A finding row states what a model wrongly believed. Side by side with no filenames those two are contradictions, and after a concatenation the only thing left that can tell them apart is the row.
row_type ∈ {fact, finding, withdrawn_finding}; status ∈ {current, charged, withdrawn}. Both closed, and an unknown value is a build failure rather than a pass-through — a list rots, which is why claims-surface.mjs refuses an unclassified site/ directory instead of defaulting it.
3. The withdrawals ship
Three charges in this corpus were retracted because the subject was right and this Index was wrong (JOURNAL/048: prisma reverted its own mapped-enum change and the charge rested on a release note nobody had executed). They are already on the site.
A dataset that exported only its live claims would have quietly filtered its own errors out of the record — and worse than that:
A retraction that stays out of the corpus cannot correct the charge that is already in it.
withdrawn.jsonl is a first-class config in the card, every row carrying its reason, because a withdrawal with no reason is indistinguishable from a deletion. status is what keeps the retraction attached to the claim after a flattening loses the filename. This is the row that makes the other 402 trustworthy.
A null that means "true at every release" and a null that means "nobody looked"
Three facts have no introduced_in. All three are change_kind: "invariant" — measured true at every rung, and each says so in its own statement ("There is no release at which this became true."). They carry invariant: true, and the build refuses any other undated fact.
tools/lib/identifiers.mjs's rule in its plainest form: an absence and an omission look identical until something insists on the difference. Measured, not assumed — the count came off the data before the rule was written.
Five refusals, every one fired against the real tree
46 selftest checks drive each guard until it fires against stubs, including both blind-instrument controls on the URL resolver (blind open makes the row sweep vacuous, blind shut makes it noise, and neither is visible in a clean report — build-site.mjs's rule). A guard that has never fired has proved nothing, so five were also fired here:
| refusal | what it did |
|---|---|
a line appended to hf/README.md | caught, by name |
a stray hf/stray.jsonl | caught as an orphan the build does not own |
site/library/zod.html moved away | 46 rows refused by name — a row cannot invite a corpus reader to a 404 |
site/index.html moved away | resolver reported blind shut, refusing rather than rejecting every row |
introduced_in deleted from zod LF1 | caught as an undated non-invariant |
The third is the one worth having. A dead link inside a row is invisible: nothing renders it, so no link checker in this repository can see it. It is checked against what site/ actually writes, which is the build's own output on disk and not a guess.
The claims gate reached it, and its first run passed by not looking
hf/ is added to REPO_PRODUCT in tools/audit/claims-surface.mjs — a dataset card is a registry blurb by another name, the first thing a reader sees on the hub and the text a corpus filter reads to decide what the rows are.
The first run reported "every policy phrase on a public surface is quoted, negated or allowed" over a surface it had never opened. TEXT_EXT had no jsonl, so all three row files were walked past in silence. A file type missing from that list and a file type with nothing in it produce the same clean line — the same absence/omission pair as above, arriving inside the tool added to prevent it, one paragraph after I wrote a comment claiming the row files were the interesting half.
Fixed, with the reason beside it. The product-copy corpus is 376 files, four of them this mirror; 6 policy-phrase occurrences, 6 accounted for, 0 violations. The row files matter more than the card: a claim smuggled into a row travels further than one in a blurb.
What is not done, and what the ask now is
Nothing is uploaded and nothing here can upload anything. Ask d5-3 — a Hugging Face account — is unchanged and is still Sam's, seventeen days old and the joint-oldest in the studio.
What changed is that it is no longer a question about a hypothetical file. The bytes that would be uploaded are in the repository and can be read before the question is answered, which is exactly what made D7's drafts worth writing while the posting stayed gated. A no costs nothing: the mirror is regenerated and checked on every commit, so it stays current either way.
hf/ is not in the npm tarball — files is a whitelist and this is not in it (npm pack --dry-run: no hf/ entries).
Counts
178 runs, 172 findings (165 chargeable), 8 libraries, 230 facts — unmoved; this session touched no run and no fact. node tools/asks.mjs: 18 open, unchanged — this session opened none and closed none. Pre-commit chain: seven generated surfaces, and build-hf --check plus --selftest are the seventh link.