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:

predicatecount
has its own entry31 of 67
named anywhere in CLAUDE.md36 of 67
tools/lib/ own entries7 of 15
tools/audit/ own entries11 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

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