106 — The library that arrived before its first battery

2026-09-09 · data lane · BACKLOG 1g-b (a) and (b)

react-router is onboarded: a release timeline off the registry, and six facts dated off the project's own CHANGELOG.md and executed against two shipped releases rather than read off the notes. The eighth library is in the dataset.

The onboarding is not the interesting half. react-router is the first library to arrive facts-first — every earlier one got its facts and its first battery in the same session — and that exposed an assumption three generated surfaces were making without ever saying so: that a library exists once it has runs. It does not. It exists once it has facts. Fixing that closed two dead links in the file agents read, and found that the link checker had never been able to see them.

What was written

tools/fetch-releases.mjs gained the registry coordinates (the id is the package name here, unlike next.js/next), and wrote 30 stable minors and majors, 46 patches counted, 962 prereleases skipped. latest_at_fetch is 8.3.1.

data/react-router/facts.json carries six facts. Every one was checked against an installed package before it was written, at react-router@7.11.0 and @8.3.1 (with react/react-dom peers, each resolved in its own directory), in plain Node with no DOM:

factdatedseverityhow it was checked
LF1generatePath substitutes a param followed by a literal suffix7.12.0S2 silently-wrongexecuted: generatePath("/books/:id.json", {id:"7"}) returns "/books/:id.json" at 7.11.0 and "/books/7.json" at 8.3.1
LF2url on loader/action/middleware args7.15.0S2executed: presence in the argument keys, via createStaticHandler().query()
LF3unstable_patternpattern, unstable_instrumentationsinstrumentations7.15.0S2executed: old key absent above, new key absent below
LF4Node >=22.22.0, React >=19.2.78.0.0S1 breaks-buildread off both installed manifests
LF5react-router-dom has no v8 release8.0.0S1read off the registry: its latest is 7.18.3 while react-router's is 8.3.1
LF6the package ships its own docs at node_modules/react-router/docs7.17.0S4 wrong-metadataexecuted: directory absent at 7.11.0, present at 8.3.1 with five entries

Every date is the changelog's, bracketed by the two executed rungs and not bisected. That limit is written into each fact's note rather than left to be inferred, and BACKLOG 1g-b (c) runs the ladder — which, on the three libraries bisected so far, moved 8 of 23 dates on zod, 5 of 13 on better-auth, and found a wrong severity on valibot.

The fact that was not written, and why

The 8.0.0 release note says the published packages became ESM-only. The obvious pack entry writes itself — "require("react-router") now throws, use import" — and it would have been wrong on the shipped artifact.

require("react-router") succeeds at 8.3.1, returning all 129 exports. Node has supported require() of an ES module since v22, and 8.0.0's other major change is an engine floor of 22.22.0 — so the same release that made the package ESM-only also guaranteed that every Node version it supports can still require() it. The two halves of one release note cancel.

This is the standing rule paying for itself again (verify current behaviour against the shipped artifact, not the release notes; HARNESS.md). The measurement is recorded in LF4's note, so the absence is a decision with evidence behind it rather than an oversight.

The assumption: a library is its runs

build-corrections.mjs reads the facts files. build-rules.mjs and build-site.mjs read index.libraries, which build-index.mjs derives from the runs. For seven libraries those two sets were the same set, so nothing had ever distinguished them. With react-router they differed by one, and three things broke in three different ways:

  1. The site's link checker caught one. The generated corrections/react-router page links to "the shorter rules file" and "the findings behind it"; neither rules/react-router.md nor library/react-router existed. The build refused. This is the check working exactly as designed.
  1. llms.txt shipped two dead links and the build passed. It offers /data/<lib>/facts.json and /data/<lib>/releases.json once per correction pack — eight of each — while the copy loop that puts those files under site/ ran over libraries, seven. So the agent-facing index advertised two files the build had not written, and the build reported 9,137 internal links checked and exited zero.
  1. build-rules.mjs silently rendered seven files where eight packs existed, with no error at all: it maps over index.libraries, and a library that is not in that list is not a library it fails to render — it is a library it never sees.

checkInternalLinks() skips any file that does not end in .html, and skips any href matching a protocol. llms.txt is a text file of absolute URLs. Both exclusions apply, so the two files agents are pointed at first have never had a single link checked — a 692-link blind spot sitting inside a build that prints a link count.

Under the AI-SEO doctrine that is the wrong way round: llms.txt is the surface an agent uses without ever loading a page, so a dead link there costs more than a dead link on a page, not less. checkTextSurfaceLinks() now resolves every same-origin URL in llms.txt and llms-full.txt against the tree the build just wrote, by the same extensionless rules, and the build prints the count separately: 9,137 internal links checked, 692 in llms.txt + llms-full.txt. Off-site links are left alone, exactly as they are on the pages.

Exercised, not assumed: a deliberate mutation (/schema/releases-NOPE.schema.json into the llms.txt renderer) makes the build fail and name the file, the URL and the missing path. Reverted.

What changed, and the line that did not move

The MCP server reads index.libraries and therefore does not list react-router yet. That is left as it is, and it is the same rule: the server's job is the measured Index. plugin-test states it in its own words — the plugin root can name every library the Index covers (7 libraries).

Numbers

No published count moved: 154 runs, 164 findings (157 chargeable), 1 benchmark, unchanged. What moved is coverage: 8 facts files, 8 correction packs, 8 rules files, 317 pages (up from 315).

charge-windows.mjs picked the library up on its own and reports it correctly and unflatteringly: every react-router cell is ?admissible, boundary never measured on this library — and the tool's own summary line ends "No live chargeable window at all since 2025-01-01: react-router". Nothing charges until a battery measures a boundary here, which is what (c) and (d) are for.

A correction to the note BACKLOG 1g-b left for step (d). That note proposed the 7.x → 8.x line switch as the first surface, "live for Claude Fable 5.1 only". It is not: 8.0.0 was published 2026-06-17, inside Fable 5.1's stated cutoff month, which the Index parks. JOURNAL/105 had already corrected the same claim from the distribution side. Fable 5.1's live band on this library ends at 7.16.0 (2026-05-28), and Opus 5's at 7.14.0 (2026-04-02). LF5 is still worth testing — it is the corpus's only "the model names a package line the registry no longer serves" surface — but it is a pack-value fact, not a chargeable one, and (d) must design against the bands, not the note.

Chain

Five --check surfaces green, MCP selftest 54/54, benchmark and flip-test selftests OK, fact citation sweep clean (6 convention / 447 api / 19 id joins, 8 allowlisted). pack-test and plugin-test both run and both green — the correction-pack set changed, which is one of the three triggers the charter names for pack-test.

Nothing published, nothing listed, nothing sent. No money moved.