116 — The release note that called the regression a fix

2026-09-10 · data lane · BACKLOG 1g-f, and 1g-g's reader vindicated from an unexpected direction

BACKLOG 1g-f wanted one thing: a fact below react-router 7.0.0, because Claude Haiku 4.5 denied the entire v7 line existed three months after it shipped and the Index had nothing to charge that miss against — every one of react-router's facts was dated 7.12.0 or later. The item's own instruction was to decide a question before installing anything, and the answer to that question turned out to be the smallest thing this session produced.

The question the item asked, and why the answer was cheap

1g-f expected the 6.x line to be unreachable: "react-router-dom was the package on 6.x and the ladder installs one release of one package per rung — the same limit that makes LF5 unprobeable."

The registry says otherwise. react-router is published on its own across the whole 6.x line, 6.0.0 to 6.30.6, seventy releases. The ladder reaches below the major under the same package name, the same per-rung-alias layout, and nothing in the runner had to change. The assumption was reasonable — on 6.x react-router is the core and react-router-dom is what an application installs — and it was wrong about the only thing that mattered, which is whether a tarball exists. One registry read, before any install.

LF8 — the first fact in the Index about a major LINE

The changelog's own words for 7.0.0 are four lines: "Collapse @remix-run/router into react-router", and the same for react-router-dom, @remix-run/server-runtime and @remix-run/testing (PR #11505). Three of those four are measurable from inside react-router alone, and LF8 is the conjunction of exactly those three, split at every and per valibot's rule:

All three are false at 6.30.6 and true from 7.0.0. The fourth changelog line — that react-router-dom became a re-export — is LF5's limit exactly and is not claimed by this row: the runner installs one release of one package per rung. It is verified against the registry instead, where react-router-dom's latest is 7.18.3 and there is no 8.x release at all.

Severity S1. A reader holding the v6 arrangement writes import { Link } from 'react-router-dom', and on the 8.x line there is no package for that import to resolve to.

The control that stopped being dull across a major

The export diff taken before anything was written says react-router exports 59 names at 6.28.0 and 111 at 7.0.0. createStaticHandler is one of the 61 gained — on the 6.x line the static handler lives in the @remix-run/router dependency and this package does not export it.

The committed control was "substitute a whole-segment parameter and run a loader for a matched route", and it is createStaticHandler that runs the loader. Unconditional, it is false at every 6.x rung for a reason belonging to the library, and the runner would have dropped all fourteen as broken installs. That is JOURNAL/108's dropped rung and JOURNAL/109's broken ladder arriving from a third direction: not a rung that will not load, but a control whose own subject has a boundary inside the ladder.

Measured before it was changed, not after: generatePath and matchRoutes are true at all 16 sampled rungs, the loader clause at 6 of 16. The clause was kept rather than deleted, because deleting it loses the guard it was written for — a 7.x rung whose loader instrument breaks would score LF2/LF3 false instead of being dropped, and a false scored for an instrument's reasons is how a boundary moves. The control now states one proposition well-formed on both lines: the library routes, and where a release ships a static handler that handler runs a loader.

The change was verified the way this repo verifies a refactor. With the ladder still at its committed 37 rungs, the bisector's --json output under the new control is byte-identical to the old one — 18,361 bytes, sha256 3caa63ad…, 16/16 confirmed. Only then were rungs added.

The row that was a uniform negative, and the verdict that caught it

LF8a's first form asserted typeof s.m[n] === "function", chosen deliberately over a bare in so that a key shipped holding undefined would fail. It came back false at all 47 rungs — including three releases the export diff had already shown exporting every one of the names.

Link, NavLink and Form are forwardRef components, and a forwardRef result is an object. Three of the eight could never satisfy the predicate, so the row could never be true anywhere.

This is BACKLOG 1g-g's rule — an instrument that returns the same answer to every input has not measured anything — arriving in a session that had queued 1g-g's adoption as a separate task. What caught it was not a human comparing against a known answer, which is how JOURNAL/110's identical failure was caught. It was the bisector's own NEVER_TRUE verdict: the machinery raised the uniform negative as a failure instead of returning it as data, which is precisely what tools/lib/identifiers.mjs was built to do for the grep-based probes. The third instrument that needed a control column had one already and nobody had noticed.

Both rows now share one exported predicate — narrower than in, wider than typeof === "function" — including LF8b, whose seven names all happen to be plain functions today. Two rows asking the same question must not answer it two ways.

The result the session did not go looking for

Extended below the major, LF1 came back NON_CONTIGUOUS with a single true cell at the ladder's floor. generatePath("/books/:id.json", { id: "7" }) returns "/books/7.json" at 6.4.0 and the pattern unchanged from 6.10.0. At minor granularity that is a one-rung island and unreportable, so the four releases between them were installed and the edge bisected at patch granularity:

correct at 6.4.0, 6.4.1, 6.4.2, 6.4.3, 6.4.4, 6.4.5, 6.5.0, 6.6.0, 6.6.1, 6.6.2, 6.7.0, 6.8.0, 6.8.1, 6.8.2 — and broken at 6.9.0 and at every release from there to 7.11.0.

So LF1 is a restoration, the corpus's third and the first outside zod. 7.12.0 did not introduce the behaviour; it restored one react-router had until 6.9.0 (2023-03-10), after an outage of two years and ten months. The published statement's clause "on 7.11.0 and below" was false of fifteen releases. The 7.12.0 boundary and the false cell at 7.11.0 are unchanged and were never in doubt — the fact was right about everything it had measured, and wrong about the range it had not.

And the release that broke it describes itself as a fix. 6.9.0's own changelog entry is "Fix generatePath incorrectly applying parameters in some cases" (PR #10078). On this input it is the release where the substitution stopped happening. The Index's standing rule is that a fact is not dated from a release note without executing it; this is the sharper form — a release note that names the opposite of what the release did, and nothing but execution separates the two.

LF1's statement, stale_belief, note and measured_range are corrected, with the 6.9.0 changelog line and the full per-release measurement added as citations. The corrected outage is transcribed into the probe's claims as 6.9.0..7.12.0 from the fact's prose, not from the cells this run printed, which is the rule the zod restorations established.

What is published

node tools/audit/bisect-facts.mjs react-router over a 51-rung ladder, 6.4.0 to 8.3.1, one release per directory resolved alone, react and react-dom pinned to 19.2.8 throughout: 20/20 confirmed, 0 to review. react-router 7 facts → 8; every one of them now carries a measured_range that crosses the major. Runs, findings and libraries are unmoved at 158 / 165 / 8 — bisecting facts charges nothing, and no battery ran.

The also_known_as collision was caught by the corrections gate rather than by review: LF8 listed react-router-dom, which is LF5's api, and build-corrections.mjs refused the build.

Two things measured and deliberately not made facts

What 1g-f still owes

The fact exists, so the Index now has something to charge Haiku 4.5's v6 denial against on a charging arm — but nothing is charged: that miss landed on an arm pre-registered as non-charging and re-reading a draw is the line JOURNAL/098 drew. Charging it needs a battery, and charge-windows.mjs will keep printing react-router's Haiku cell against its 6.28.0 boundary with 6.29.0 live. That battery is the next chunk, not this one.