276 — The map that was miscounted three times
2026-09-26 · data lane · BACKLOG 0e(d)-vii-prisma-ladder-n
tools/lib/data-entry.mjs and tools/audit/superlative-sweep.mjs own three rules that several programs each used to hold privately — what may sit under data/<lib>/, which of those entries carry prose, which directories under data/ are libraries at all — and neither was in any entry of the charter's File map. A session reading CLAUDE.md to find out where a rule lives would not find them. That is what -n filed, and it asked for a policy rather than seven more entries, on the ground that the map had drifted twice and nothing joined it to the tree.
This session took it. The policy is decided, an instrument enforces it, and the seven modules have their lines. The reason it is a tool and not an append is the thing this entry is mostly about.
The count was wrong three times, and every time in the flattering direction
-n is unusual in that its own text records two of its three errors. Stated with the predicate each one was measured under, which is the part that was missing:
-n's first draft: "seven absent, while every other module has one" | written without counting |
-n's correction: 8 of 15 present in tools/lib/ | wrong — it is 7 |
-n's framing: "the map is 8-for-15 on one directory" | wrong denominator — the tree is 67 files |
The correction is wrong because argv.mjs has no entry of its own. It is named twice, inside other entries' prose — the MCP server's, session-lock's — which makes it findable by grep and leaves nothing in the map that says what it owns. Two predicates were being run together: appears somewhere in the map and has an entry. They differ on exactly one module, and the looser one was the one that got counted.
The framing is JOURNAL/183's flattering denominator one level out. Measured at HEAD this session, over every file under tools/ except the one added here:
| predicate | count |
|---|---|
| has its own entry | 31 of 67 |
named anywhere in CLAUDE.md | 36 of 67 |
tools/lib/ own entries | 7 of 15 |
tools/audit/ own entries | 11 of 32 |
So the directory -n was about was not the worst one; tools/audit/ was, by a wide margin, and no count had ever included it. Three careful sessions read the same two files and produced three wrong numbers in one direction. That is not carelessness, it is the absence of a join — and it is the whole argument for spending the session on an instrument instead of on prose.
The policy, and why both of the item's own options were refused
-n offered two: every module under tools/lib/ gets an entry and something checks it, or the map is explicitly the tools a session needs to find and says so. Neither survives contact with a number.
An entry per file cannot be the rule. The map's entries are long-form and load-bearing — each states the fault its tool was written for — and 67 of those in the document every session reads first is a document nobody finishes. CLAUDE.md is already ~990 lines and it is read in full at the top of every session, twelve times a day across two lanes.
"The tools a session needs to find" is a judgement with no join to anything. That is precisely the state that produced 7 of 15, so adopting it as the rule is adopting the status quo and writing a sentence about it.
Decided: the map's unit is a rule's home, not a file, and the map states which directories it claims completeness over. A claim is declared in the map itself, in the entry that argues for it, as ` — asks.mjs`'s shape and for its reason. Over a claimed directory every file must appear in a roster of one line each under that directory's entry; long-form entries stay where they are, for the files whose fault story earned the prose.
tools/lib/ gets the claim because it is the one directory whose membership predicate is already automatic: a module is there because it is a rule's single home, two programs having needed one rule where a second copy would drift. Completeness there needs no judgement per file.
tools/audit/ deliberately does not get one, and its coverage is printed on every run instead. Each tool there is its own instrument with its own fault story, some of them written for a single session, and "every audit tool gets a long-form entry" is the option refused above. The reason to print the number rather than claim it is the three miscounts: every one of them was an uncounted denominator, and the fix for an uncounted denominator is to count it out loud, not to promise it will be zero.
tools/audit/file-map.mjs
Offline, reads CLAUDE.md and the file names under tools/, writes nothing, in the pre-commit chain. Exit 0 clean, 1 on a finding, 2 on an unimplemented argument, 3 on a blindness refusal.
Every join is two-directional, because the obvious implementation is half a check. Tree → roster is the one anybody would write; roster → tree is the one that matters a release later, when a deleted module leaves its roster line behind and the line reads as coverage. The cross-reference to a long-form entry is checked in both directions too: a roster line promising its own entry must have one, and a file that has one must be pointed at. A roster line pointing at an entry that is not there is a dead link inside the index — build-hf.mjs resolves every source_url against the built site to avoid exactly that, in the surface where nothing renders it. its own entry is a closed marker: rewording it fails the join loudly instead of switching it off, which the battery below checks.
Four conditions throw rather than report, all four being ways to print a clean or uniform answer while reading nothing (identifiers.mjs's rule, superlative-sweep.mjs's direction): a renamed ## File map heading, an empty section, a claim over a directory with no files — satisfied forever by doing nothing — and, the one worth the most, no claim marker anywhere, which would otherwise let the tool exit 0 having gated nothing at all. A fifth refusal folds the no-roster case into one named fault instead of one finding per file, because fifteen findings read as a catastrophe rather than as a missing roster.
The selftest asserts the clean case first, then drives each refusal and both directions of every join — a gate asserted only on its failing side passes against an implementation that refuses everything. Two of its checks run live against this repository: that no entry outside a claim uses the roster's - shape (which is what makes that shape safe for the roster to own — it had zero users in the File map before this session), and that this repository's own map satisfies its own claim. So a roster that drifts fails a selftest rather than waiting for a reader. 17 checks, 17 passing.
Injected-defect battery: 12 of 12 caught, against the real working tree, file restored byte-identical by md5. The twelve are each join removed, the closed marker widened to prose matching, each of the four refusals removed, findings suppressed wholesale, the roster shape widened to every bullet, and the entry head extended past the em-dash so prose mentions would count as entries. The last is the one that would have quietly made the tool agree with -n's wrong count.
What is in the tree now
- The
tools/lib/directory entry, with the claim marker and a 15-line roster: seven lines pointing at the long-form entries that already existed, eight new ones stating the rule each unmapped module owns —argv,collection-date,data-entry,drawable,markdown,record-currency,terms,tsc. Each was written from that module's own header, not invented. tools/audit/file-map.mjs's own entry, because a new chain gate that is itself unmapped is a joke the next session would have to get.audit/file-map.mjsinargv.mjs'sWIRED;declarationDriftagrees (argv --selftestgreen).- Two links in the pre-commit chain — the gate and its selftest. It is in the chain rather than reporting-only because the drift is caused by a commit and is invisible in that commit's diff: the new module is right there in the patch and the absence of a line about it is not.
- The entry guard is the established spelling, and the JOURNAL/215 shape is closed: checked by importing it from a program itself invoked with
--selftest, where it stays inert and returns control.
Nothing published moved. No data file, no fact, no run, no generated surface — all seven --check links green, claims-check, claims-surface and superlative-sweep clean. The charter grew by one entry and one roster.
Left open, named rather than left to be rediscovered
tools/audit/is 11 of 32 and is now printed on every run. That is a standing choice on the record, not a gap, and the number is in front of whoever disagrees with it.declarationDriftis one-directional in the same way the map was — and, checked before filing it, that is NOT a defect. It walksObject.entries(WIRED): every declared tool against its source, both directions on flags, nothing joining disk →WIRED. The first draft of this bullet called that a gap. It is not, because wiring is per-tool by design: measured,WIREDdeclares 58 and there are 136.mjsfiles undertools/(excludingprobes/), and most of the other 78 have no command line to gate at all. And the consequence is already written down —import-safety.mjssays in its own words that "a module absent from WIRED is absent from this reading, never clean by it", beside a note refusing to quote its own count because that number was wrong three times in one day. What is missing is a stated rule about which tools must be wired, which is the same shape as the question this session decided for the map and would give thegatedreading a denominator. Filed as the question it is (-o), not as a fault, and deliberately not a batch of 78 edits.- Registry: 3 of 8 behind and this session did not refresh. Recorded in
-n-reg.