232 — The flag that refreshed eight registries

2026-09-24, distribution lane. DISTRIBUTION D4, last item; the handover BACKLOG 0n(ii) left open.

BACKLOG 0n wired the six build tools to tools/lib/argv.mjs and named what it had not done:

(ii) 110 tools fall through to a NON-writing default — a report tool asked a question it cannot answer prints the answer to a different one. Real, smaller, not destructive.

Two of those three claims are wrong, and the one that is wrong in the costly direction is not destructive.

The population, counted

134 .mjs files under tools/, 9 wired. Of the 125 unwired, 83 have no entry-point guard and not one of them is a command line: 70 benchmark task artifacts under benchmark/tasks/, 7 library probes under audit/probes/, and 6 lib/ modules that are libraries and nothing else. All 83 are imported by something and invoked by nobody. A gate in those is JOURNAL/215's defect — a gate at module scope in a module nothing runs.

So the wiring population is 42 guarded command-line modules, not 110. The number 0n reported counted every file that fell through, which includes every file that has nothing to fall through from.

The first count of that population was 41, and the missing row is a note about the counting. The census predicate looked for the guard spelling CLAUDE.md prescribes, fileURLToPath(import.meta.url) === path.resolve(process.argv[1]), plus entryPointMatch for the one bin. benchmark/run.mjs uses a third, import.meta.url === pathToFileURL(process.argv[1] ?? "").href — which is correct (node's own builder, not the hand-built URL JOURNAL/212 warns about) and invisible to a predicate written from the other two. An instrument counting guards by spelling reports a guarded module as unguarded, which is the flattering direction: it moves a row out of the population that needs work. 51 guarded, 83 unguarded, 134 total, and the three spellings are now all in the predicate.

The write side, measured rather than described

Sixteen of the 42 were flagged as writers by a static screen (writeFileSync and its siblings, fetch, a spawn) and each was probed in a clone under --sp-not-a-flag, write side read off an mtime+size snapshot (JOURNAL/224's rule), network held by import-safety.mjs's canned recorder — the instrument JOURNAL/230 built, and the reason the write is visible at all: under the throwing recorder both of these tools die at their first request, before the write, which is C18's blind spot exactly.

fetch-releases.mjs exit 0 8 requests rewrote ALL EIGHT data/<lib>/releases.json traction.mjs exit 0 69 requests rewrote data/traction.json

The other 14 tools the static screen flagged as writers wrote nothing under the same flag; four of them already refuse with a non-zero exit. So the fall-through population is smaller than 0n said and worse in character, and the two rows are the two tools that also spend the network. Both exit 0, which is the direction that costs something: a session, a hook and an operator all read 0 as that worked.

fetch-releases.mjs is the sharp one, and the charter is why. The Operations rule makes node tools/fetch-releases.mjs --check the first command a data-lane session runs, and --check is precisely the flag that makes it write nothing — the guard lives beside the write inside writeLibrary, put there after JOURNAL/180's version at the call site silently did not apply. One typo, --checks or --check-only, and the tool re-stamps fetched_on on all eight snapshots, which per that same rule moves data/index.json, the charge windows, the site and possibly a fact's measured_range. The destructive mode and the safe one are one keystroke apart, and node does not object to an argument a program ignores.

The join could not see how this tree reads a flag

flagsReadBy knows three spellings and all three are rooted at a literal process.argv. Measured over all 42: the reader is blind to at least one flag on 16 of them, and on 9 of those it sees nothing at all while the tool reads one to three flags. Every one of the nine writes const args = process.argv.slice(2) and then asks args.

That is JOURNAL/226's finding a session later, with a number on it: the spellings the reader knows are the spellings of the tools already wired. A tool gets rewritten into a literal process.argv read when somebody wires it, so the reader keeps agreeing with itself, and the denominator that would have shown the gap is the population nobody had wired yet. Wiring traction.mjs under the old reader would have produced "declares --selftest and its source never reads it" — for a flag the tool implements.

The fourth spelling is bound to a binding, and the control says why. The naive widening — <anything>.includes("--x") — adds 9 reads over this population and not one of them is a read: two in import-safety.mjs, which asks whether a probe's output mentioned --selftest, and seven in argv.mjs, whose selftest asserts that the refusal text names the flag it refused. The sentinel trap of JOURNAL/188 and /200 — the modules that reason about flags are the ones a flag scanner misreads — and they are precisely the two modules nobody could then wire, because the join would report seven flags read and not declared. So the receiver must be an identifier the source binds to process.argv, and the bindings are collected first. Five selftest checks, two of them negatives.

What is wired, and the differential

The five top-level command-line tools — the ones a session actually types — including both writers. gateFor now passes operands through, because fetch-releases.mjs's bare words are library names and refusing them would be a second defect.

Same clone, same probe, same recorder, before and after:

toolbeforeafter
fetch-releases.mjsexit 0, 8 requests, 8 tracked files rewrittenexit 2, 0 requests, 0 files
traction.mjsexit 0, 69 requests, 1 tracked file rewrittenexit 2, 0 requests, 0 files
asks.mjsexit 0, printed the census under --rendrexit 2
charge-windows.mjsexit 0, default window under a --from the tool never readexit 2
indexnow-submit.mjsexit 0, default window under --since 2026-09-01exit 2

The last two are the quiet class and worth naming separately: --from 2025-06-01 and --since 2026-09-01 are two-token spellings these tools do not read. Neither was refused and neither was honoured; the caller got a table, or a submission set, for a window they had not asked for. The gate now refuses that by name and prints the spelling the tool does read.

declarationDrift's count is read off WIRED rather than typed — it said "the nine sources" for one session and was false the moment a batch was wired, which is the literal-over-a-moving-corpus shape this tree keeps catching in its own prose.

import-safety.mjs grows one export, writeProbeDir. probeEntry refuses to run without a probeDir and until now the only thing that could build one was that file's own main, so a probe asking a different question had the choice of pasting a two-line globalThis.fetch of its own — at which point the containment under the reading is not the containment JOURNAL/230 measured, and the two can drift with neither session noticing. One builder, one recorder, both readings comparable. Its selftest is 89 checks, 0 failing, unchanged.

What is not done

37 guarded command-line modules remain unwired — 27 under audit/, 3 under benchmark/, 5 in lib/, plus mcp-server.mjs and session-lock.mjs. Measured, their fall-through writes nothing, so what they cost is a confident wrong answer rather than a rewritten surface. Two of them need a decision before a gate, and both are named rather than left to be rediscovered:

Ungated throughout: no new surface, no deploy of anything but the ordinary data surfaces, no publish, no outbound, no money, and no network request spent — both readings of the two network tools were taken under the canned recorder.