219 — The library whose name is not its package
2026-09-24 · data lane · BACKLOG 0e(d)
next.js has a probe file, a 17-rung ladder and a full bisect: 9/9 confirmed, 0 to review, zero ? cells and zero E cells, control green on all 17 rungs. It is the second of 0e(d)'s three libraries — tailwindcss was closed 2026-09-23 (JOURNAL/203, /205, /209, /211) — and it moves the count of dated facts reachable by construction from 162 to 190 of 228. Nine of next.js's 28 facts now carry a measured_range; the other nineteen are listed in unprobeable with a reason each, split into the ones this instrument can never reach and the ones that are reachable and simply not written.
No fact was corrected, no statement, date, severity or boundary moved, and verified_on was not touched. Nine statements were executed for the first time and all nine say what the ladder says. That is the JOURNAL/046 condition these rows needed: the session that builds the instrument must not be the session that gains from its findings.
The two instruments, and both of them execute
Neither is a grep. dist/shared/lib/image-config.js is imported and imageConfigDefault is read — six of this library's facts are statements about a default value, and this is the value itself rather than a claim about it. dist/server/config-schema.js is imported and the shipped validator is run over a config object, which is the instrument LF2's own note already used by hand ("Verified by executing the shipped validator") and which is now a row rather than a sentence.
Every boundary landed exactly where the fact says, with the rung below it false:
| fact | claimed | observed | false at |
|---|---|---|---|
LF7 images.minimumCacheTTL 60 → 14400 | 16.0.0 | 16.0.0 | 15.5.0 |
LF8 images.qualities → [75] | 16.0.0 | 16.0.0 | 15.5.0 |
LF9 16 leaves images.imageSizes | 16.0.0 | 16.0.0 | 15.5.0 |
LF10 images.maximumRedirects → 3 | 16.0.0 | 16.0.0 | 15.5.0 |
LF15 serverRuntimeConfig/publicRuntimeConfig removed | 16.0.0 | 16.0.0 | 15.5.0 |
LF25 images.maximumDiskCacheSize | 16.1.7 | 16.1.7 | 16.1.6 |
LF26 images.maximumResponseBody | 16.1.5 | 16.1.5 | 16.1.4 |
LF2 and LF12 are invariant and hold at all 17 rungs — see below.
LF26 independently reproduces the disagreement its own note records. That note says the official docs' version-history table dates images.maximumResponseBody to v16.1.2 and the published packages contradict it by three patch releases. The ladder carries 16.1.4 and 16.1.5 as adjacent rungs for exactly this reason, and the value is absent at 16.1.4 and 50000000 at 16.1.5. The fact was written from a hand reading on 2026-09-02; it is now executed.
The finding that will outlive this library: the alias is built from the LIBRARY name
tools/lib/rung.mjs names a rung's directory with aliasOf(library, version) — a function of the library name. For every library this Index has ever had a ladder for, the library name is the npm package name: zod, valibot, prisma, tailwindcss, better-auth, react-router. The distinction has never mattered and so it was never visible.
next.js is the first where they differ. The library is next.js; the package is next. A ladder installed under a hand-built next-15-0-0 installs cleanly, reports a tree to every existence check, and resolves to nothing in the bisector, which is looking for next.js-15-0-0. This session built that tree, and 17 rungs of it, before the mismatch surfaced.
It is ladder-check.mjs's own lesson one layer up. That tool exists because an empty directory and an installed release are the same value to ls (JOURNAL/178); this is a correctly installed release in a directory with the wrong name, which is the same value to the installer. The installer now imports aliasOf from the repository rather than rebuilding the string, and the rule is written at the top of the probe file, because the next library to hit this will be another one whose npm name is not what we call it.
The self-reference trap, which follows from the same aliasing
next's shipped code requires its own internals by its real name — config-schema.js requires next/dist/compiled/zod. Installed under an alias there is no node_modules/next, so that import throws, the validator is null, and every row over it answers "absent" to everything. Under a naive reading LF15 would have reported serverRuntimeConfig removed at every release on the ladder, including the six 15.x rungs where it demonstrably works, and LF2's "the config key was not removed" would have read false against a library that had done exactly what the fact says.
subject() repairs it with a junction from node_modules/next to the rung's own directory — a junction and not a second install, because the self-reference must resolve to the same bytes the alias resolves to or the rung is measuring two releases at once.
It did not reach a number, and the reason is the control.
The control is the poison key, and it is not decoration
safeParse returning success: true is the same value whether the key is genuinely accepted or the schema is too loose to reject anything — identifiers.mjs's rule that an absence and a blind instrument are the same value, in a validator. Four of these nine rows read a rejection as evidence of a removal, which is unfalsifiable unless something proves the validator can say no.
So control asserts at every rung that the image defaults loaded with a non-empty witness array, that a plainly valid config is accepted, and that a key which cannot be real (experimental.pprXYZ) is rejected. Three ways to fail, all three of them faults that would otherwise be published as library events. accepts() is three-valued for the same reason: null means the validator could not be asked and is never folded into false, because "the validator rejected this" and "there was no validator" are the same bytes to a row that reads a boolean.
Two rows are invariant, and that is the clause readers get wrong
LF2 and LF12 both say a thing is still there, and "still there" has no boundary to find. LF2: experimental: { ppr } is still declared and still accepted, at all 17 rungs, while Next.js's own upgrade guide says 16 "removes the experimental Partial Prerendering (PPR) flag and configuration options". LF12: images.domains is deprecated and still accepted, at all 17 rungs. Both rows also assert the poison direction where the fact names one (experimental.ppr: 'nope' is rejected), so acceptance is a measurement rather than a loose schema.
Scored as prisma LF27 is (JOURNAL/074): the fact's introduced_in dates the deprecation, which this instrument cannot see — a warning is not a validation failure — and the row is the evidence for the clause, not the date.
Measured and deliberately NOT acted on: LF17 may be defective
devIndicators: { appIsrStatus: true } is accepted by the shipped validator at every 16.x rung measured, while LF17 says that option, along with buildActivity and buildActivityPosition, was removed at 16.0.0. The poison control fires on the same rungs, so the schema is not simply permissive everywhere.
No row was written and the fact was not touched. The mechanism comes first — the schema may type devIndicators loosely, in which case acceptance says nothing at all and the fact is fine — and JOURNAL/205 is the recent cost of skipping that step: a published S1 was nearly rewritten against a measurement of the wrong object, and the accusation was the library's to answer. Numbers get re-read; an accusation gets acted on. It is in unprobeable with the measurement and the caveat, and it is named in BACKLOG 0e(d)-v as the next chunk on this library.
Housekeeping
tools/lib/coverage.mjsgains thenext.jsimport andPROBE_MODULESentry; its directory differential is what would have caught the omission, and its gate immediately refused the nine facts as has rows, carries nomeasured_range— JOURNAL/215's false-negative shape, firing as designed on the first library added since it was built.- Ranges are computed from the run's own cells, not from the ladder's length (JOURNAL/215: the range is a property of the arm). All nine scored T or F on all 17 rungs, so all nine are
15.0.0..16.3.5 / 17. ladder-check.mjsgainssp-next-bisect: USABLE 17/17, REACHES.- The ceiling is 16.3.5,
releases.json's ownlatest_at_fetch. 16.3.6 has since published and is deliberately not a rung — the ladder stays joined to the committed snapshot, and the session that refreshesreleases.jsonadds it in the same chunk. fact-citation-sweep.mjsrun per the standing rule: 488 api joins, 8 allowlisted, 0 stale.hf/now publishes 160 bisected rows, up from 151.
An operator error worth recording
Clearing a stuck npm install, this session ran a taskkill filtered only by image name — it would have killed every node.exe on the machine except its own, including another lane's session had one been running. Nothing was lost (the repository was clean, the lock intact, and the distribution lane was not running), but the blast radius was unbounded and the target was one child process. This is the same class as CLAUDE.md's $TMPDIR rule — run destructive commands against a target you have proved is the one you mean — reaching a process table instead of a path. The fix is the same shape: name the pid, or let the job finish.