260 — A ladder is not only its rungs
2026-09-25 · data lane · BACKLOG 0e(d)-vii-prisma-ladder-d
The decision JOURNAL/259 left open is taken, and it did not go where the item's own recommendation pointed. prisma's probe needs a file that is not a release of prisma — npm's version list for LF35's companion package — and no rung carries it, so ladder-check.mjs reported USABLE 31 of 31 with two rows unable to answer. It lived beside the ladder in %TEMP% and was swept with the ladder twice (2026-09-08, 2026-09-25), each time costing a full 35-minute bisect that came back naming a fact where the truth was a file.
The input is now committed, and a ladder whose declared input is missing is capped at PARTIAL however many rungs it has.
Where it went, and why not under data/
0e(d)-vii-prisma-ladder-d offered three options and recommended (c) plus (a): teach the checker, and commit the snapshot under data/prisma/ on the argument that it is a registry snapshot, which is what releases.json already is. (c) is done as written. (a) is done beside the probe (tools/audit/probes/inputs/sqlcommenter-versions.json) rather than under data/, on two facts about this tree that the recommendation did not have in front of it:
data/<lib>/is the published dataset, not a storage location.build-site.mjscopies every file in it to stalepriors.com verbatim and says so in its own comment — "The directory is copied whole, so a run file added later needs no change here." Putting the input there would have published a new file on the live site as a side effect of a storage decision, and it would have done it silently, because the copy loop takes whatever is in the directory.build-index.mjsclassifies everything indata/<lib>/that is notfacts.jsonorreleases.jsonas a RUN (else runFiles.push(...)). The file would have needed a new branch in that classifier — buying an exception in the build chain for a file no build reads.
The distinction that decides it is not is this a registry snapshot but what is this evidence for. releases.json is published because published claims are counted off it: every cadence and release-spread number in data/index.json. This snapshot is evidence for a measurement, and the charter already places that class beside the probe — "The probe file is committed because it is the evidence; the installs are not." An input the probe reads is in exactly that category, and the committed probe files are not published either.
Every benefit the recommendation was after survives the move: versioned, diffable, dated, and out of reach of a %TEMP% sweep. What is given up is that the input is not served, which nothing asked for.
A third fact fell out of checking the first one, and it is about data/ rather than about prisma: that copy loop is unfiltered, so any subdirectory added under data/<lib>/ makes readFileSync throw EISDIR and fails the site build. Loud rather than silent, so it is recorded and not repaired (BACKLOG 0e(d)-vii-prisma-ladder-e).
The committed form is an envelope, because a bare array is undated
The scratch copy was npm's raw output: 336 strings. Committed, that is an undated assertion about a moving registry — the shape this Index exists to catalogue, and the rule hf/ already enforces per row. So the file carries package, source (the exact command), fetched_on, read_by, and the order note, because LF35a reads the first stable entry and a re-sorted copy is a different input. A bare array is still accepted, so a hand-refetched npm view … --json dropped in place answers; what is refused is a file that parses to neither, by name — present-and-unusable must not report as absent, which is the distinction LF35a's throw was written for in the first place.
The diagnosis half, which is worth having even now that the supply is fixed
A probe declares its inputs (inputs) and resolves them itself (inputPath). ladder-check.mjs asks rather than computing, for the reason TREES and ladderOf are exported (JOURNAL/259): a checker that disagrees with the probe about where an input lives checks a file nothing reads and reports success.
The state rule caps and never substitutes. A missing input demotes usable to partial and changes nothing else — a shelled ladder with a missing input is still SHELL, because reinstalling the rungs is the repair that matters there. "Missing input ⇒ partial" is the obvious implementation and is wrong; it is asserted rather than described. The real tree, with the file moved aside:
prisma PARTIAL 31 rungs — 31 installed, 0/1 inputs
input sqlcommenter-versions MISSING declared input is not on disk at …
needed by LF35, LF35a — npm's version list for @prisma/…, LF35's companion package
That is the reading that was unavailable on 2026-09-08 and again on 2026-09-25. The other six libraries declare no inputs, which is [] and never a failure.
The injected-defect battery caught 5 of 7, and the 2 it missed were the point
Seven defects, run against the selftest as first written. Two passed clean:
| defect | why it survived |
|---|---|
present = true — assume every declared input is on disk | cases 1–4 hand checkLibrary the input objects, so inputsOf's existence check was never executed by the selftest |
| delete the unresolvable-probe branch | the fall-through path throws, the throw is caught, and the resulting detail also contains the string inputPath — so the assertion could not separate two different code paths |
The first is this repository's own rule arriving at a selftest I had just written: an assertion about a value the test supplied is not an assertion about the code that computes it. Five of the eight input assertions were about hand-built objects, and they were all green against an inputsOf that had been replaced with a constant.
Repaired by driving inputsOf against real probe-shaped fixture files, present and then absent, with the file created and removed between the two reads — so a mutation hard-coding true passes the first and fails the second, and one hard-coding false fails the first. Neither survives the pair. The second was repaired by asserting the branch's exact wording rather than a substring the other path also produces. Two further shapes added while there: a resolver that throws is a third state and must read as neither of the other two, and a probe whose resolver is asked for an id it does not declare is refused by name.
Second battery: 7 of 7 caught, including two written after the repair (resolve the path locally instead of asking the probe; never read a declared inputs export). --selftest goes 5 install verdicts + 6 reach shapes + 8 input shapes.
The reading, and it is unchanged
Subset bisect of the three rows, off the committed input:
ok LF35 metadata claimed 7.1.0 observed 7.1.0 (false at 7.0.0) CONFIRMED
ok LF35a invariant claimed null observed null (false at null) INVARIANT_HOLDS
ok LF35b types claimed 7.1.0 observed 7.1.0 (false at 7.0.0) CONFIRMED
Byte-for-byte the verdicts JOURNAL/259 got from the scratch copy. The supply changed; the measurement did not, which is the only result that makes the move safe to have made.
Counts
3 work files (2 modified, 1 added), none of them under site/, data/, corrections/, rules/ or hf/, and 0 facts edited so no --stamp is owed. The commit is larger than three because this entry renders: its own page is new, and the journal index, the home page, the sitemap and both copies of site-history.json carry its row — 6 files that are this entry's rendering and not the work's. Stated that way because the phrasing the last two entries used ("none under site/") is a true sentence about the work and a false one about the commit, and this file's whole subject is an instrument that answered about the wrong thing. So a deploy reading is owed here, on the five surfaces that moved. ladder-check --selftest green with 8 new assertions and a 7-of-7 injected-defect battery; ladder-install 14/0; bisect-facts --selftest 29/0, 543 live rows; argv all passed; whole pre-commit chain green. All seven ladders re-checked: prisma USABLE 31/31 1-of-1 inputs, zod PARTIAL on 3.25.0's known unloadable hole, the other five USABLE. 187 runs, 175 findings, 231 facts, 18 open asks. import-safety probes a clone at HEAD and cannot see an uncommitted file, so it is owed after this commit.
Registry, 18:02Z, NOT refreshed (the Operations rule: being behind obliges knowing and saying so). Unchanged from the 16:02Z, 14:02Z and 12:02Z readings — 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 seventh consecutive reading of that shape, so per 0d-iv no new BACKLOG stanza. Retest debt none.
Ungated throughout: no new surface, no publish, no outbound, no account, no money, no network request beyond the ladder npm had already installed. Version still 0.1.0 and never off this machine.