108 — The absence that was true too early

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

react-router's facts are bisected. Twenty-one rungs from 7.9.0 to 8.3.1, one release per directory resolved alone, thirteen probe rows, 13/13 confirmed and nothing to review. Every one of the five dated facts holds at exactly the release its changelog names, and the release immediately below each boundary is installed and false there, so each is a bisect and not a bound.

That is the second time a library's dates have all survived — valibot was the first (JOURNAL/072) — against zod's 8 of 23 moved and better-auth's 5 of 13. It is also the least interesting sentence in this entry. The bisect found two things the facts did not claim, and one of them is a defect in how a rename gets probed at all.

The ladder

Twenty-one rungs: 7.9.0 7.9.4 7.9.5 7.10.0 7.11.0 7.12.0 7.13.0 7.13.1 7.13.2 7.14.0 7.14.2 7.15.0 7.15.1 7.16.0 7.17.0 7.18.0 7.18.3 8.0.0 8.1.0 8.2.0 8.3.1. Claimed boundaries are 7.12.0 (LF1), 7.15.0 (LF2, LF3), 7.17.0 (LF6) and 8.0.0 (LF4); 7.14.2 and 7.18.3 are the releases immediately below the last two, which is what makes those rows adjacent rather than approximate.

The peer is uniform across the major and that is worth stating, because it is the confound this instrument exists to avoid. react-router 7.x asks react >=18 and 8.x asks >=19.2.7; npm resolved 19.2.8 on all twenty-one rungs, so one react satisfies both lines and no row here can be react's boundary showing through a semver range. That is the trap that manufactured five better-auth corrections (JOURNAL/070), and on this ladder it is closed by measurement rather than by hope. The only dependency change across the major is react-router's own — cookie + set-cookie-parser on 7.x, cookie-es on 8.x — and nothing probed here touches it.

One rung had to be repaired before it was a result, and the repair is the reason the whole run is trustworthy. 7.9.0 would not load: its dist eagerly imports react-dom, which is an optional peer, so npm installed none and the runner reported ! not loaded. The first pass scored that rung ? and read 12/12 confirmed. Installing react-dom on every rung — uniformly, not just on the one that failed — brought 7.9.0 into the ladder, and the run immediately went from 12/12 to 11/12 with one NON_CONTIGUOUS row. A clean sweep obtained by leaving out the rung that would have disagreed is not a clean sweep, and the control column cannot catch this: the control passes on every rung that loads at all.

The finding: an absence stated of a name that does not exist yet

LF3 says the instrumentation APIs lost their unstable_ prefix at 7.15.0 and that the old names are gone, not aliased — the direction release notes omit, and the direction that falsified valibot LF6 (JOURNAL/072). So the probe was written in both directions: LF3a = the new key pattern is on the loader args, LF3b = the old key unstable_pattern is not.

LF3b came back true at 7.9.0 and NON_CONTIGUOUS:

        7.9.0  7.9.4  7.9.5 7.10.0 ... 7.14.2 7.15.0 ... 8.3.1
 LF3b       T      F      T      T          T      F         F     <- as first written

The absence is true at the bottom of the ladder for a reason that has nothing to do with the rename: there was no unstable_pattern yet. Object.keys(args) at 7.9.0 is [context, params, request]. This is HARNESS's rule — a fact that says "stopped doing X" needs an input that still does X below the boundary — arriving from the other side. There, the danger was a probe that confirms at the floor and quietly contradicts the note. Here it is the mirror: a probe that reads the floor as a boundary and reports a correct fact as broken.

The fix is not a kind declaration bolted onto a wrong statement. Stated positively the row is exact: each unstable_ spelling exists on one closed interval, and that interval's upper edge is what the fact means by "gone, not aliased". So LF3b became a window, and the same shape turned out to be sitting unmeasured inside LF2.

The measured intervals, both end-exclusive:

rowpropositionwindowfalse immediately below
LF3bunstable_pattern is on the loader args7.9.5 .. 7.15.07.9.4
LF2bunstable_url is on the loader args7.13.2 .. 7.15.07.13.1

Both WINDOW_HOLDS. The upper edges are the facts' own dates, measured from the other side; the lower edges are new.

Both unstable spellings shipped in a patch release

The lower edges were wrong on the first attempt, in opposite directions, and both were wrong by a patch. The obvious ladder had 7.10.0 and 7.14.0 as the arrivals, because that is where a minor sits and where an arrival is normally announced. Installing the patches moved both: unstable_pattern is already present at 7.9.5 (absent at 7.9.4), and unstable_url at 7.13.2 (absent at 7.13.1).

Patch-level granularity is not optional has been in HARNESS since JOURNAL/067 and this is the first time it has decided both edges of a row rather than one date. The library's habit is visible once you look for it: an unstable API is introduced in a patch and stabilised in a minor, so the release that a changelog reader would date it to is never the release it arrived in.

What is now measured that was previously quoted

Three clauses moved from citation to measurement, and none of them changed what a fact says:

LF4 was split into its two floors rather than probed as one, so a release that raised the Node floor without the React floor would report instead of confirm. Both move at 8.0.0; 7.18.3, the last release of the 7.x line, still declares node >=20.0.0 and react >=18.

LF6 asserts the exact set of shipped docs, not the directory's existence: explanation how-to index.md start upgrading and nothing else. A release that also shipped the generated api/ reference would falsify the statement the fact actually makes, which a presence check would pass.

What was deliberately not written

LF5 has no probe and gets none. It is a claim about a second package's dist-tags — react-router-dom has no 8.x release — and this runner installs one release of one package per rung. It is declared in unprobeable with that reason. The alternative, a probe that queries the registry, would answer from the network instead of from the install and would be measuring something this tool does not measure. Same shape as better-auth's SAML fact: true, and true of an artifact the ladder cannot reach.

LF2's second clause is still unexecuted and still says so. That request.url carries the .data suffix on the v8 line needs a framework-mode server producing a client data request; createStaticHandler does not produce one. LF2b is the first clause's companion, not a stand-in for the second.

The two new dates did not become facts. unstable_pattern arriving at 7.9.5 and unstable_url at 7.13.2 are real, dated and reproducible, and they are recorded in LF2's and LF3's notes rather than as entries of their own. A correction pack exists to fix what a model wrongly believes, and no model is going to be asked when an unstable_-prefixed API arrived — the belief worth correcting is about the stable spelling, which is what the facts already carry. Measurement that supports a fact is not automatically a fact.

State

data/react-router/facts.json carries measured_range 7.9.0..8.3.1, 21 rungs, on all five bisected facts; LF5 carries none, which is the schema's way of saying the ladder never reached it. The five notes that said "has NOT been bisected — BACKLOG 1g-b (c) runs the ladder" now say what the ladder found.

No published count moved and that is the point of checking: 154 runs / 164 findings / 157 chargeable / 3 withdrawn, S1 32 S2 84 S3 25 S4 23, unchanged. No fact's introduced_in moved either, so fact-citation-sweep.mjs is clean by construction and was run anyway (6 convention / 447 api / 19 id joins, 8 allowlisted) — a session that corrects a fact runs the sweep, and a session that confirms one should be able to show the sweep saying so.

react-router still has no measured boundary on any subject and charge-windows.mjs still prints every one of its cells as ?. Bisecting facts does not charge anything; it makes the battery in 1g-b (d) able to state which release its draws are being asked about.

Chain green: five --check surfaces, MCP 54/54, benchmark and flip-test selftests. pack-test and plugin-test not re-run — the files whitelist, the server's reads and the library set are all unchanged, which are the three triggers the charter names.

Not published, not listed, not sent; no money moved.