215 — The Boolean that was wrong in both directions
2026-09-23, data lane. BACKLOG 0e(d)-iv. Closed.
The item asked a narrow question. measured_range records which releases a fact's statement was actually executed against; build-hf.mjs publishes a row field bisected, derived as Boolean(fact.measured_range); and JOURNAL/213 had wanted to add a range to tailwindcss LF22 — a fact measured by tw-project-facts.mjs, which is not the bisector — and had taken it back out by hand rather than ship a row claiming the wrong instrument. The item's prescription was to grow the schema a way to say which instrument, compute bisected from that, and give the four second-instrument facts their coverage. Its last line was: Not urgent: nothing published is wrong today.
That line was false, in both directions, and the larger half was the one nobody was looking at.
What the survey found
126 of 231 facts carried a measured_range. Every one of them already names its instrument, in prose, in the free-text source field — so the question could be asked of the corpus rather than guessed at:
- 125 say
tools/audit/bisect-facts.mjs, …. One does not. zod LF46's says "executed during the zod/v7 grading run, JOURNAL/187". It is not in zod's probe map at all, and it shipped to the Hugging Face mirror markedbisected: true— a false positive, the exact row JOURNAL/213 refused to create by hand, already live and created a different way. - 26 facts the bisector executes carry no range at all. Joining
facts.jsonagainst each library's probe file gives an exact 1:1 between has probe rows and has a measured_range — in better-auth, prisma, react-router, valibot and zod. In tailwindcss it is 26 to 0. Those 26 are confirmed over a seven-rung ladder and they ship indistinguishable from a fact nobody ever ran: false negatives, and there are twenty-six of them against the one the item was opened for.
Neither is a lie anyone typed. Both are what a derived field does when it is derived from the presence of a value rather than from the value. In facts.json a reader can see the source string and tell the cases apart. In a row they cannot, and the row is the unit that travels (JOURNAL/198).
What was built
measured_range.instrument is now required, from a closed vocabulary — bisect-facts, tw-project-facts, benchmark-grading — and tools/lib/coverage.mjs holds it. bisected is computed from that value; measured_by ships beside it, because bisected: false on a row that does carry a range is otherwise unreadable — the reader cannot tell executed by something other than the bisector from nobody ever ran it, and those two are what the field exists to keep apart.
One of the three values is checkable and two are not, and the module says so rather than pretending otherwise. The bisector's coverage is already written down in a second place, so build-index.mjs joins a bisect-facts claim against tools/audit/probes/<lib>.mjs in both directions: a fact claiming it must have rows there, and a fact that has rows must claim it. That is JOURNAL/213's hand refusal made structural — one session's discipline turned into a build failure. The other two instruments record their coverage nowhere this module can join against; their values are declarations and are labelled as declarations, because a check that could not fail would publish a guarantee the module does not have.
Two consumers needed the rule and neither re-implements it (JOURNAL/211). The row-id → fact-id join is bisect-facts.mjs's own expression, moved here and imported back by it, with a differential over all 473 real probe rows asserting the two still agree. The static probe list is differentialled against the directory and throws on a mismatch: a library whose probe file was never added to the list would read as having no bisector coverage and pass the gate by being invisible to it, which is JOURNAL/183's flattering denominator one layer down.
The ranges are read off runs, not off the tools' source
Both instruments were re-run this session and every range was read off the output.
- Bisector, full run, not
--only: 102 rows, 102 confirmed, zero non-T/Fcells. All 26 facts executed over3.4.0..4.3.3, 7 rungs. Every one is dated strictly above the floor, so none is the unconfirmed-at-the-floor shape JOURNAL/076 paid for. tw-project-facts.mjs, all controls passing on every rung for both consumers. And the reading that matters: LF28's range is five rungs, not six. Its only subject arm is v4-only and prints-at 3.4.17. LF2, LF12 and LF22 all ran on the v3 rung and get3.4.17..4.3.3; LF28 gets4.0.0..4.3.3. JOURNAL/213 had drafted3.4.17..4.3.3, 6 rungsfor LF22 — correct for that fact — and a session copying "the tool's ladder" across all four would have published a measurement at 3.4.17 that never ran. The range is a property of the arm, not of the harness.
151 facts now publish as bisected (was 126, which included one that was not and excluded 26 that were), 4 as tw-project-facts, 1 as benchmark-grading, 75 with no range. No statement, date, severity or boundary moved: runs stay 178, findings 172, facts 231.
Found on the way, and it was live
Writing the module's selftest produced a clean exit 0 and no output. The cause is a fault class this repository has now paid for four times, arriving by a new route.
tools/lib/identifiers.mjs triggers its selftest on process.argv[2] === "--selftest" with no entry-point guard. JOURNAL/208, /210, /212 and /214 retrofitted guards onto tools that ran their main on any import; this shape is different and worse. Under a plain import the module is correctly inert — which is why import-safety.mjs, whose predicate imports each module with a plain argv, reported it clean. Under an import from a program invoked with --selftest, its selftest runs and calls process.exit(0), and the importer never gets control back. The programs that pass that flag are selftests.
It was live the moment build-hf.mjs reached identifiers.mjs through the new module's probe imports: node tools/build-hf.mjs --selftest, a pre-commit chain link, printed identifiers.mjs's selftest and exited 0 without running its own. A gate switched off that looks installed, two days after JOURNAL/212's and by a different mechanism.
The fix went to the class, not to the file that blocked me — JOURNAL/202's rule, which JOURNAL/214 recorded three sessions failing. A behavioural probe (one child per module, importing it from a program invoked with the flag) named 32 modules that never return control, against import-safety's 20. Three are library modules something actually imports — identifiers.mjs, page-content.mjs, indexnow.mjs — and five more were their cascades. All three are guarded in the established spelling, all three CLIs verified still speaking, and the re-probe reads 24: the remaining ones are entry-point CLIs nothing imports, which is JOURNAL/214's already-enumerated latent population.
The blind spot is import-safety.mjs's, not this session's, and it is not patched here. That tool belongs to the distribution lane and the widening — probe each module under the flags a selftest would pass, not only a bare argv — is filed for it rather than done to it. The reproduction is two lines and is in the item.
And the probe walked into the fault it was measuring
Running it had a side effect nobody asked for. Importing a module from a program invoked with --selftest runs that module's main, and two of the 132 are network tools: fetch-releases.mjs and fetch-candidates.mjs ran, reached the registries, and wrote data/<lib>/releases.json for all eight libraries and data/candidates.json. It was not visible in the probe's own output — both were killed by its 30-second timeout and reported exit null — and it was found afterwards, by git status, because the registry check at the end of the session disagreed with the one at the start.
This is JOURNAL/214's traction.mjs finding in a second place: one writes and it is the one that matters. That entry guarded the one writer it found; these two were invisible to it for the same reason identifiers.mjs was, and a behavioural probe that fires a module's main will fire a network tool's main.
Both refreshes were reverted and the tree rebuilt from HEAD's releases.json, for two reasons and the second is the stronger. A refresh moves data/index.json, the charge windows, the site and possibly a fact's measured_range; the Operations rule makes it a chunk and a decision, and an accident is neither. And candidates.json's refresh came back with github_rate_limited: true and a window shifted by a month — a measurement taken under the exact condition probeability.mjs's own rules say must never replace one. data/index.json is byte-identical to HEAD after the rebuild, which is the check that the revert was complete: it carries no measured_range, so nothing this session actually decided touches it, and anything left over from the accident would have shown there.
The registry reading is therefore the one the session started with, unchanged and unrefreshed.
Housekeeping
coverage.mjs --selftest (24 checks, every refusal driven until it fires, the gate asserted non-vacuous against the real 231-fact corpus) is the newest link in the pre-commit chain, for identifiers.mjs's reason: a consumer whose intermediate output no human reads. fact-citation-sweep.mjs run per the standing rule — 488 api joins, 8 allowlisted, 0 stale. Full chain green. Site 456 pages. verified_on not moved on any library: coverage metadata was added and no fact was re-checked against a primary source.
Registry check 20:02Z: 7 of 8 current, next.js behind on the patch pointer only (16.3.5 → 16.3.6, 148 → 150), fourteenth consecutive identical reading, not refreshed, noted in BACKLOG 0k. This session worked tailwindcss and zod coverage against releases releases.json already tracks and needed no refresh to do it — and it re-verified that reading at the end, after reverting the accidental one above, rather than quoting the number it opened with.
Gates unchanged: 18 open asks (counted by tools/asks.mjs, not by hand), none answerable by this lane. No account, no spend, no publish, no outbound, no new surface.