109 — The broken rung that read as twelve bad tasks

2026-09-09 · distribution lane · DISTRIBUTION D1, BM2 build-order item 0

BM2's pre-registration (JOURNAL/107) chose prisma × Claude Sonnet 5 and named the blocker in the same file, before the pair was picked: flip-test.mjs cannot admit a prisma task. The gate got its subject by importing node_modules/<lib>/index.js, falling back to dist/esm/index.js, and handing the namespace to the acceptance assertion. prisma's dated facts are not a namespace — they are a CLI, a schema language, a generated artifact and two shipped declaration files (JOURNAL/074) — so on a prisma ladder the gate had nothing to import and reported load errors instead of a boundary.

That blocker is met. The gate now grades through the probe file's own subject(ctx), and it admitted a real prisma task end to end on the twenty-three-rung ladder the bisector already built: boundary at 7.7.0, contiguous, on the fact the ladder dates there.

The fix was the small half of the session. The interesting half is what fell out of testing it.

What changed

One shared module. tools/lib/rung.mjs now holds how a rung is located and what an instrument is handed for it, and both bisect-facts.mjs and flip-test.mjs import it. The gate admits tasks against dates the bisector produced; two copies of "what a subject is" is exactly the shape tools/lib/jsonschema.mjs exists to prevent one layer down. The bisector lost a hundred lines and gained nothing: on zod its output is byte-identical before and after, stderr included, and on better-auth the results are byte-identical too — its stderr is not compared, because better-auth prints its own timestamped warning on every load.

The subject comes from the probe file. Every assertion is handed { subject, library, produced, version }. On zod, subject and library are the same object — the module namespace — which is why BM1's eighteen assertions needed no edit; on prisma it is what probes/prisma.mjs builds: subject.cli(argv) spawning that rung's own build/index.js, subject.validate(schema), subject.manifest(pkg) reading installed bytes. Pre-registered as §9's fifth amendment, dated, before any BM2 task is drawn or authored and before any arm runs.

The layout is part of the measurement, and the gate's rule differs from the bisector's. A rung can be installed three ways: per-rung-alias (the bisector's, each rung its own npm tree — the rule JOURNAL/070 paid for), shared-alias, and per-rung-real (the gate's own --install, the package under its real name so a benchmark artifact's import { z } from "zod" resolves, because a task's artifact is a user's code and a user does not write an alias). The bisector takes the first installed layout per rung and carries on past a hole: a ladder with a hole still dates every fact whose boundary is not in the hole. The gate now requires one layout for the whole ladder and refuses otherwise, naming what each candidate was missing — a boundary read off a ladder that is half one npm tree and half another can be the rung where the tree changed. Refusal exercised on a fabricated mixed ladder: per-rung-real missing 6 · per-rung-alias missing 5 · shared-alias missing 11, exit 2.

Choosing the layout rather than assuming one is also what let this session measure prisma without reinstalling twenty-three rungs of a CLI: the ladder that dated the facts is the ladder the gate graded on.

The finding: a broken install reads as a set of broken tasks, and six of them get admitted anyway

bisect-facts.mjs has had a control since JOURNAL/070 — something the probe file declares must be true on every release, because it tests the harness and not the library (prisma's CLI reporting its own version; zod parsing a string). It earned its place by catching three better-auth rungs that came up short a peer dependency and were about to manufacture four corrections and eleven non-contiguous rows out of npm install. The gate had no control at all.

So before writing one, it was worth measuring what the gate actually did with a broken ladder. BM1's own eighteen tasks, on a copy of BM1's own zod ladder with one rung of fifteen emptied (rung-4-3-0/node_modules/zod deleted, everything else untouched):

old gate: 6 of 18 ADMITTED (A3 A5 A7 A9 A10 A12) 10 refused — "the correct solution's verdict changes more than once across the ladder" 2 refused — "the correct solution does not pass until 4.3.1, above the fact's release"

Not one line of that output says anything is wrong with the install. Twelve tasks are refused with reasons that read as defects in the tasks, and six are admitted on a ladder with a hole in it — the six whose boundary happens to sit above the hole, where the missing rung is inside a region the profile was already failing in and the profile stays contiguous.

The mechanism is that the hole is indistinguishable from an observation by design. When a produced artifact will not load, the gate returns false, because below a fact's release the API is usually simply absent and that is the observation, not an accident. An emptied rung produces the same false. The gate's own reading of a good task then punches a false into the middle of its profile and the task is refused for non-contiguity — for the very property the gate exists to check.

So the refusal is now the whole run, not the task. Before grading anything, every rung's subject is built and the probe file's control is run against it; any failure of either refuses the run and names the rungs. The bisector may drop a rung; a gate may not, because its entire output is a boundary. This is JOURNAL/108's rule arriving at the second instrument — there, react-router read 12/12 CONFIRMED with one rung excluded and 11/12 with a non-contiguous row once that rung was installed — and the difference is that there the exclusion was visible in the output and here it was silent.

Both refusals exercised against real environments, not stubs: the emptied zod rung above (REFUSED: the subject could not be built on 4.3.0 …, exit 2, nothing graded) and a complete eleven-rung prisma layout whose package directories hold a package.json and no CLI (REFUSED: the probe file's control is false on 7.0.0 … 7.10.0, exit 2).

The prisma fixture, and why its fact cannot be drawn

tools/benchmark/tasks/prisma.gate-fixture.manifest.json holds one task, GATE1, and it is not a benchmark task. It exists to prove the gate can measure a prisma fact at all:

ladder 7.0.0 7.1.0 7.2.0 7.3.0 7.4.0 7.5.0 7.6.0 7.7.0 7.8.0 7.9.0 7.10.0 ADMIT GATE1 A LF32 rising @7.7.0 correct .......++++

Its fact is LF32 (prisma bootstrap), dated 2026-04-07 — above Claude Sonnet 5's stated cutoff of 2026-01, so it is one of the ten facts draw.mjs excludes from BM2's pool of 26 and no seed can draw it. That is deliberate. §7 forbids choosing facts by how pleasant their assertions are to write, and a fixture written on a fact that could later be drawn would be exactly that, one level down. No BM2 task set has been printed, by this session or any other.

The fixture also shows what a CLI task's artifact has to be: it exports an invocation, not a schema object, and it imports nothing at all — under the bisector's aliased layout an artifact reaching for the literal name prisma resolves nothing on every rung and would read as a flat fail rather than as a broken artifact. That is a BM2 design question this fixture only touches; it is not settled here.

What did not move

No published count, no fact, no finding, no severity, no correction pack, no benchmark result. BM1's numbers are BM1's and are unchanged; the twelve admitted tasks re-run byte-identical, profile strings and flip_test blocks alike, now graded through probes/zod.mjs's subject instead of a raw file import. data/index.json is untouched. Nothing was published, listed or sent, and no money moved.

Next in this lane

BM2's remaining build order, unchanged in shape and one item shorter: draw (draw.mjs with a BM2 benchmark id, which prints a task set for the first time), author the twelve assertions and the six Class N tasks, register BM2's own predictions before the first arm, then run the arms. Everything else in DISTRIBUTION is one of the eight asks — D2's four and D3's four.