264 — The recipe two installers read, and the peer that was seven years wrong

2026-09-25 · data lane · BACKLOG 0e(d)-vii-prisma-ladder-b and -c

Two items sat below the one that closed last session, both left by JOURNAL/259 as latent defects with named repairs, both about what an installer knows. They turned out to be one chunk, and the second one's own framing was the thing worth correcting.

-c first, because the obvious fix was the dangerous one

peers is what a probe file says must be installed beside the library on every rung. It has existed since prisma got one, ["@prisma/client", "@prisma/adapter-pg"], and both installers turned it into npm specs the same way:

...peers.map((q) => ${q}@${version}) // ladder-install.mjs ...peers.map((q) => q + "@" + v) // flip-test.mjs

That is at the library's own version, and it is right for prisma, whose companion packages are versioned in lockstep with it. It was also the only declaration that had ever existed, so the rule and the field were indistinguishable: nothing in the tree could tell whether peers meant "these packages" or "these packages at this version".

next.js separates them. Its rungs carry react and react-dom at 19.2.0 at every rung, while next itself runs 15.0.0 to 16.3.5. Under the lockstep rule a declared "react" becomes react@16.3.5 — which is not a failure, because React 16.3.5 is a real package, published in 2018. A peer wrong by seven years installs. So the repair the backlog asked for would have produced a tree that installs cleanly, reports seventeen rungs to every check in this repository, and measures next.js beside a React seven major versions below the one the published readings were taken against.

So the fix is a vocabulary, not a peers line. peerSpecs() in tools/lib/rung.mjs — the one module both installers already import — takes either form:

"@prisma/client" -> at the library's own version (lockstep; what the field used to mean) "react@19.2.0" -> at its own version, verbatim

The separator is an @ at an index above 0, because a scoped name begins with one and "@prisma/client" must not parse as a version of nothing. That rule has a hole its own first run found: "@19.2.0" — a version whose package name is missing — has no @ above index 0 either, so it fell through to the bare branch and became the spec @19.2.0@16.3.5. A scoped name contains a /; that is the second guard, and it is the one the injected-defect battery below fires against.

And then: are the peers needed at all?

The item called this unmeasured, and it is the question that decides whether declaring them is evidence or noise. A peer declared because the tree has it and a peer declared because the subject needs it are different claims.

Measured, not assumed. A second ladder was installed with next alone at the two ends of the real one — 15.0.0 and 16.3.5 — and all 69 probe rows plus control were run against both trees, one rung per process, because this probe file's own fifth instrument spawns a child per rung for exactly that reason.

release tree control true false rows differ 15.0.0 with peers true 22 47 69 15.0.0 next alone true 22 47 69 0 16.3.5 with peers true 66 3 69 16.3.5 next alone true 66 3 69 0

0 of 138 readings differ. And neither reading is a blind uniform — 22/47 at the bottom of the ladder and 66/3 at the top, identical in both trees — which is what makes the agreement a measurement rather than two instruments failing the same way.

So peers is declared, and it is declared for a stated reason that is not "the subject needs it": it reproduces the tree the published readings were taken on, which is the reason ladder-install.mjs carries a closure differential at all. A rung where npm refuses these peers is still a usable rung, and the probe file now says so.

-b, and the negative control that is the whole check

flip-test.mjs --install skipped a rung when the package directory existed. Handed a shelled ladder — the 32,253 empty directories a %TEMP% sweep leaves behind (JOURNAL/178) — it printed have <lib>@<v> for every rung and installed nothing. The one tool that can repair the gate's tree reported it already repaired.

The plan is now installPlan(), pure and split out from the doing, reading classifyRung's verdict and not comparing it to one value:

verdictactionwhy
installedkeep
shell, absent, wrong-versioninstall
unloadablerefusezod 3.25.0 ships src/ and points at dist/ (JOURNAL/185) — reinstalling changes nothing

Asking only for installed, which is what the backlog item proposed as the cheapest form, is how a session retries that install four times. The vocabulary is ladder-install.mjs's planFor's, word for word: the two installers own different layouts and must not own different vocabularies.

The check that matters is the negative control. The selftest builds a shelled fixture and asserts the new plan installs both rungs — and then re-implements the predicate it replaced over the same fixture data and asserts that it keeps both. Without that second assertion the check passes against either implementation, which is the condition that hides this class of defect; it is the same shape JOURNAL/262 added to collection-date.mjs the same day, arrived at independently.

Both installers read the rule and neither keeps a copy

Asserted over the two sources rather than left to the imports: a regular expression for the mapped concatenation, run against both files, with the expression that used to be there as a positive control. That control had to be assembled from two string pieces, because written as one literal it is itself an occurrence and the check failed against its own control on first run — flip-exposure.mjs's rule that a scanner written out of the shapes it looks for finds itself, one file along.

Readings

One new defect, named and measured rather than fixed

flip-test.mjs guesses the npm package name from the library name — install() builds the spec library + "@" + v, and LAYOUTS["per-rung-real"].pkg is (library) => library. That is the trap ladder-install.mjs's header documents from the other side, and packageNameOf exists because seven of eight libraries agree and next.js does not.

It is loud, and that is measured: next.js is a real npm package — version 1.0.3, unrelated — so npm install next.js@16.3.5 is an ETARGET and not a silent install of something else, and the resolver's half refuses with "no single layout holds every rung". Reach today is zero: the two benchmark documents are BM1 (zod) and BM2 (prisma), and both library names equal their npm names.

Not fixed here because the repair is not the installer's alone — the resolver reads the same name through rungPaths, so the two move together, and a signature change to rung.mjs is shared by the bisector, the gate and the runner and is only verifiable against an installed per-rung-real ladder of a differing-name library, which does not exist and would cost seventeen next installs. Recorded as BACKLOG 0e(d)-vii-prisma-ladder-g with that reach.

The registry

fetch-releases.mjs --check at 22:02Z: 3 of 8 behind — better-auth 1.7.6, next.js 16.3.6, prisma 8.0.0-rc.17 — all eight held 2026-09-22, no stable minor or major anywhere. The eighth consecutive identical reading (08:02Z through 22:02Z), so per 0d-iv nothing is owed and no new stanza. Not refreshed: a refresh moves data/index.json, the charge windows and the site, and is its own chunk. Retest debt none.

Ungated throughout: no new surface, no publish, no outbound, no account, no money. Two network actions and both are registry reads — two npm installs into a scratch directory for the peer measurement, and one npm view next.js for the paragraph above. Version still 0.1.0 and never off this machine.