#!/usr/bin/env python3 """doc_census_check.py -- does a tool's own docstring census match the census that tool PRINTS? WHY THIS EXISTS This archive's three scanners each carry a census table in their module docstring: counts that a reader cites, that no command checks. The docstring is prose; prose is not executed. On 2026-10-16 the control plane returned exactly this defect against a sibling scanner, in its own words: "The scanner's own docstring carries a number that does not match the census it prints -- the exact class the tool exists to catch." tools/citation_resolve_check.py said `471 UNRESOLVED` on one line and `441 UNRESOLVED` nine lines later, over a census the tool prints as 471. Nothing was checking the docstring against the output beside it. That is this tool's whole job. WHAT IT MEASURES (read-only: this tool only writes --json) Two independent verdicts per tool, and they do not share evidence. 1. INTERNAL SPLIT -- one unit, one scope, two values. Within one docstring, every numeric claim of the form ` ` is collected; two claims of ONE unit that a single revision scope covers, carrying two different values, is a split. This catches the 441/471 instance. A split is GRADED only when a leg verified the unit (`CHECKABLE`: the run printed it with exactly one value); every other split -- no oracle (`LABEL_ONLY`, `--no-run`), a unit no leg prints (`NOT_A_CENSUS_UNIT`), a unit the tool prints per section (`SECTION_SPLIT_BY_TOOL`) -- is printed with its values as ADVISORY and never reaches the gate. 2026-10-18: the old build failed `--fail --no-run` on the 471/441 split at fca3f2e (rc 1) while the full run could not see either value, so rc 1 meant "could not check this". 2. ORACLE -- the tool is RUN, at the revision its own docstring names, and each claim is compared against the value that run prints for that unit. MATCHES claim == printed value STALE claim != printed value, and the printed unit is not declared clone-local -- a defect claim CLONE_LOCAL claim != printed value but the tool's own output marks that unit clone-local ("blobs in THIS clone's local object store"), so drift is the documented expectation, not a defect. Reported, never counted as one. AMBIGUOUS the tool prints that unit with 2+ values at that rev (e.g. `citations` prints once per section), so there is no single oracle value; both are printed and no claim is made NOT_PRINTED the unit does not appear in this tool's output at all A claim is ` ` with the unit word ADJACENT, which is the archive's own notation. A bare number is not a claim and is not checked. A WRAPPED PAIR IS A CLAIM ONLY WHEN A LEG CAN BIND IT. A number that ENDS one line with its unit opening the next is the same claim the archive writes on one line, and until 2026-10-20 this reader was line-local: a docstring reflow retired the claim silently while every command exited 0 (measured -- the pinned pair in `queue_claim_check.py` fell out of the census and the claim count fell with it, with no verdict moving). The reader now joins the pair, under the same refusals the adjacency reader already applies: a number welded to a preceding `-`, `/`, `.`, `:`, `_` or letter is not standalone, so a date's tail and `UTF-8` and `0/1` are not counts, and a unit with a hyphen behind it is a compound adjective. Whether the joined pair is SPENT is a second question, and it is answered by the census rather than by a lexicon of English: the pair is graded only when some leg prints its unit UNDER THAT SPELLING, and every pair no leg binds is printed as WRAP_UNBOUND and counted in the banner, never dropped. The spelling is exact and that is deliberate: a plural fold (`notes` -> `note`) is affordable for a one-line claim, because both sides are census voice, but a wrap is this reader's hypothesis about two lines of prose. Measured at HEAD 2026-10-20, the fold spent `the queue's own reply 62 / notes it` and graded a correct sentence STALE 62-vs-7; with the fold refused, that pair is WRAP_UNBOUND and the one wrapped claim a leg really binds -- `1,062 CONTENT_SHA256` at `citation_hash_class.py:53` -- is the only one graded. The other five candidates are named, not graded: `69 filenames` (`checkin_check.py:14`), `18 cells`, `24 spendable` and `0 without` (all three in this file -- its `--selftest`, fast-mode and `--fail` paragraphs), and `62 notes` (`queue_claim_check.py:119`). With `--no-run` there is no leg, so no wrap is spent and each one is reported unbound -- a mode that cannot see a census cannot bind a claim. TWO ORACLE LEGS, AND WHY THE SECOND ONE EXISTS Leg 1 is the tool's own run at the block's revision, exactly as above. Leg 2 is the SAME TOOL'S `--selftest`, added 2026-10-16 after the control plane returned a docstring claim of `13 cells` against a selftest that prints `18 cells` -- the defect class this tool exists to catch, sitting inside its own default scope and still read as NOT_PRINTED, because no leg ever passed `--selftest` and a cell count is printed by nothing else. A claim is adjudicated by the FIRST leg that prints its unit; NOT_PRINTED now means no leg printed it. A tool that takes no `--selftest` simply has one leg -- a fact about the tool, not a defect in it. A leg that exits non-zero having printed nothing is reported RUN_FAILED, never read as an empty census, and it adjudicates nothing either: the block's claims are still read off any surviving leg -- leg 2 -- and are left unadjudicated only when no leg survives, because a failed run is not a reason to stop looking. A revision that will not materialise fails the same way rather than producing a census at all: an empty queue is a census of zeros, and reading it as that revision's census is the phantom this rule refuses. A HEX RUN INTRODUCED AS A DIGEST IS NOT A REVISION. A block that quotes a truncated sha256 in its prose is a size, not a place: read as a rev it files the block under a revision that cannot exist, fails that leg, and then adjudicates every claim in the block again beside the real ones. Two signals are read, both local to the token -- a digest word in front of it (`sha256 `, `hash `), or an ellipsis behind it (`…`), which is how this archive writes a truncated digest. See `block_revs`. The run leg is `