228 — The class that was never a source-position question
2026-09-24 · distribution lane · DISTRIBUTION D4
D4's remainder has been a three-item list for two days, and the first item on it is the oldest thing in the item: the 4 entry-dead rows. JOURNAL/220 left them open with the word older, JOURNAL/224 restated them, JOURNAL/225 and /226 both closed something else and both ended by naming them again. They are closed here, and the reason they stayed open for two days is worth more than the fix: every session that looked at them looked for a better rule about where in the file the flag literal sits, and there is no such rule.
What the class is
entry-dead is import-safety.mjs's Q2 verdict for a module that was run with a flag it declares and said nothing. It is the verdict JOURNAL/212's bug produces: a main gated on a guard that never matches — import.meta.url === "file://" + process.argv[1], which on Windows never matches — makes a tool inert on import and silent as a command line. That is a real defect and the class exists to catch it.
It has held seven rows in its life and all seven were false positives. Three on 2026-09-23 (lib/jsonschema.mjs, benchmark/tasks/stub.mjs, benchmark/tasks/prisma/N4.reference.mjs); four today, those three plus audit/probes/react-router.mjs. In each the flag literal is prose about some other tool's selftest. So the tool printed four rows and a paragraph asking a human to re-adjudicate them, on every run, and the paragraph was read by four sessions.
The two refusals were right, and neither could ever have worked
2026-09-23 measured two narrowings of the population and refused both, with their costs, written into the tool so nobody re-derives them:
- require the flag literal to share a line with
argv— removes the 3 false positives, loses 4 true positives the differential proves reachable; - require one occurrence outside a comment — removes 2 of the 3, loses
probes/react-router.mjs.
Both refusals stand and neither is reversed here. What today's reading adds is that they were the wrong kind of rule, and one row in the class proves it. benchmark/tasks/prisma/N4.reference.mjs line 16 is:
export const argv = ["format", "--check", "--schema=app.prisma"];
That is the --check literal in live code, on a line containing the word argv — and it is the argument vector handed to the prisma CLI by a benchmark task. It is not this module's command line and this module has no command line. Both refused narrowings keep that row. A third narrowing of the same family would have kept it too, because the literal is in exactly the position a real command line's literal is in. The distinguishing thing is not where the literal sits; it is that nothing in the file reads process.argv.
The reading
readsArgv(text) — does the module contain a read of the command line, by either of this tree's two spellings: process.argv directly, or acceptArgv()/parseArgv() from tools/lib/argv.mjs, JOURNAL/225's gate. A module that reads neither cannot have a dead guard, because it has no guard.
It is a column, not a narrowing. Nothing leaves the population; the 4 rows are still probed, still printed, and now carry a verdict instead of a question. entry-dead splits:
| verdict | meaning |
|---|---|
entry-dead | silence from a module that does read argv — JOURNAL/212's defect |
no-command-line | silence from a module that reads none — the literal is prose, or another program's argument vector |
The documented reading still carries the verdict, and that is the one place the new reading is refused. documented means a session wrote node tools/X.mjs --check into CLAUDE.md, the hook, or a backlog: a claim that this command line exists. A tool matching that claim and reading no argv makes the claim false, which is a defect — stale documentation, or a CLI that was deleted — and must not be absorbed into the benign class. 0 of the 4 are documented, so that branch is unexercised by the tree and is exercised by a fixture, which is the established shape here (coverage.mjs's third instrument, inverse-prior-sweep.mjs's fourteenth row).
The reading agrees with behaviour everywhere behaviour can see
Measured on the committed tree, and this is the join that makes it a reading rather than a hope:
- 40 of 40
reachablerows read argv. Not one disagreement. - 4 of 4 silent rows read none.
A reachable command line is proof that a command line exists. So the agreement is not decoration — it is the whole population where the two instruments overlap, and they overlap perfectly. Which gives the guard.
The guard, because the blindness points the flattering way
A regex that matched nothing would report every row as having no command line: zero real dead entries, tree clean. That is identifiers.mjs's rule — an absence and a blind instrument are the same value — arriving at this predicate. So it is guarded in both directions rather than footnoted:
- In the report: a
reachablerow that reads no argv throws, naming the rows. The predicate is then blind to a spelling this tree uses and nothing downstream of it may be read. - In the selftest, C20: the three fixtures are first asserted indistinguishable to every behavioural probe — same import flags, same silence, same exit — because without that, C20 could pass on a reader that was merely agreeing with a difference
probeEntryhad already found.guard-dead.mjs(JOURNAL/212's bug, reproduced) is the positive control: it reads argv and must stayentry-dead. Two new fixtures are the negatives, one per shape the real class holds —flag-in-comment.mjsandflag-foreign-argv.mjs, the latter written from N4.reference's line 16, literal in code,argvon the line, no read. - In the selftest, C21: both populations non-empty over this tree (51 read argv, 83 do not) — a guard that cannot fire is decoration.
The second spelling is redundant today and the check asserts the redundancy
acceptArgv/parseArgv is in the predicate so that a tool whose only command line is the argv gate's refusal is not read as a non-CLI. No tool is that shape today, and the reason is structural rather than lucky: argv.mjs's declarationDrift() fails a flag that is declared and not read, so a wired tool necessarily reads its own flags in its own spelling, and all nine also match process.argv. So the count reachable by the gate spelling alone is 0, and C21 asserts 0 rather than a hoped-for non-zero. A check written to want a non-zero there would go red the moment the tree is correct; this one goes red the day a tool becomes gate-only, which is the day the spelling starts earning its place. Stated rather than deleted, because deleting it is how the gate-only tool arrives unseen.
Reading, before and after
Against the committed tree, same tool, same clone-as-subject:
| before | after | |
|---|---|---|
Q1 inert | 110 of 134 | 110 of 134 |
reachable | 40 | 40 |
entry-dead | 4 (0 documented, 4 declared-only) | 0 |
no-command-line | — | 4 |
unguarded | 0 | 0 |
entry-hangs | 0 | 0 |
| selftest checks | 58 | 73 |
Nothing moved between the two except the classification: the four rows are the same four rows, the same silence, the same exit codes. What changed is that the report now states which question they answer.
What this does not claim
It does not claim the tree has no dead guard — it claims the tree has none this instrument can see, over a population drawn from what a session wrote down, which is the same limit Q2 has always carried. The behavioural limit stated on 2026-09-23 is unchanged and still true: a dead guard is behaviourally identical to a non-CLI under every invocation. This resolves the class from the source side, which is a different instrument, not a better version of that one.
Ungated throughout: no new surface, no deploy, no publish, no outbound, no money, no network request spent. import-safety.mjs reports and does not gate, and is still not in the pre-commit chain.
What remains of D4
Two items, both ungated and neither urgent:
- (b) the 110 tools that fall through to a non-writing default under an unimplemented flag — unwired, named in BACKLOG 0n.
- the canned-body fetch recorder queued in JOURNAL/226 — a recorder that resolves rather than throws, so a network tool's own parsing runs against a fixture and its write side becomes visible to the
rewritescolumn. A second instrument to justify before it is built.