tools (volt): a sha256 prefix in a block's prose is not that block's revision

The two-leg tool read every REV_LONG token in a docstring block as a revision of
this repository. A block that quotes the queue's size as "/ sha256
`fbe51445a89f81ab…`" therefore named a revision that cannot exist: the leg raised
`git show fbe51445a89f81ab:WORK-QUEUE.md -> rc=128`, the block was counted as a
leg failure, and -- because the oracle loop runs once per rev -- each of its
claims was emitted AGAIN beside the real ones. Two spurious failures and six
duplicate rows, in the tool whose whole job is to make a docstring figure
adjudicable.

WHAT IS LANDED (908 -> 952 lines, 45,034 -> 47,501 B, sha256 5c69c2d2 ->
c90ffab4; the diff is one new helper, three call sites, three selftest cells and
the docstring paragraph that states the rule):

1. `block_revs(text)` -- the revisions a block names, with hex runs read as
   digests excluded. Two signals, both local to the token: a digest word in
   front of it (`sha256 <hex>`, `hash <hex>`, `sha1`/`md5`/`digest`), or an
   ellipsis behind it (`<hex>…`), which is how this archive writes a truncated
   digest. A FULL sha256 was never at risk: REV_LONG is {7,40} and a 64-hex run
   has no word boundary inside it.
2. `check_tool` (the live path) and `_fixture_checks` (the fixture) both call it,
   so the fixture and the tool read revs by the same rule.
3. Three selftest cells: each signal exercised alone, with the ordinary citation
   between them as the negative control.
4. The docstring says so. It also states the one silent downgrade this rule can
   cause -- a revision cited WITH an ellipsis is read as a digest and the block
   falls back to HEAD -- and that side is measured, not guessed: `grep -nEo
   '[0-9a-f]{7,40}(…|\.\.\.)' tools/*.py` returns 8 tokens at HEAD and every one
   is a digest or an oid quoted as a token, never a revision.

MEASURED DELTA, all 7 tools at HEAD, before = c90ffab4's predecessor run
(census-final.json) vs after (census-after.json), compare_rows.py:

  rows          111 -> 96   (removed 18, added 3, changed 0)
  leg failures    2 -> 0
  verdicts      8 AMBIGUOUS / 16 MATCHES / 75 NOT_PRINTED / 2 REV_UNCHECKED /
                10 SELF_NOT_RUN
            ->  8 AMBIGUOUS / 16 MATCHES / 60 NOT_PRINTED / 2 REV_UNCHECKED /
                10 SELF_NOT_RUN
  per tool      citation_hash_class.py 28 -> 19 rows, queue_claim_check.py
                34 -> 28 rows, the other five unchanged

Every removed row is a duplicate emitted under a digest; every added row is the
same claim re-scoped to the rev the block actually names. No verdict CHANGED --
the fix removes rows, it does not re-judge any. The queue block is now read at
`28ca8e5` and `29a4872`, the two commits it cites, instead of at those commits
plus the two digests it quotes.

TWO INSTRUMENTS, NOT ONE. `--selftest` is 33 cells / 33 passed / 0 failed, rc 0
(30 -> 33 with the three new cells), and it is the instrument that pins the rule
in isolation. The rule's effect on live text is pinned separately by the scoped
live run, 1.8 s: `--tools tools/queue_claim_check.py` yields 0 leg failures and
28 rows, against 2 and 34 for the same file the commit before. A cell for the
rule plus a run of the rule, because the last defect in this tool was a rule its
own fixture could not see.

FOUND WHILE MEASURING, and worth a line because it cost a red run: an auxiliary
script named `inspect.py` written beside a copy of the tool shadows the stdlib
`inspect` for anyone importing it from that directory (`dataclasses` does), and
the tool died with a traceback that named argparse. The copy is `census_rows.py`
now; the lesson is that the scratch directory is sys.path[0] for the scripts in
it.
This commit is contained in:
volt committed 2026-10-16 22:55:27 +00:00
1 parent 0031a7831f
commit f051659581
1 file changed
+48 -4
+48 -4
View File
@@ -59,8 +59,15 @@ TWO ORACLE LEGS, AND WHY THE SECOND ONE EXISTS
only when no leg survives, because a failed run is not a reason to stop 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 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 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 (measured as that revision's census is the phantom this rule refuses.
2026-10-16 against a sha256 prefix read as a revision).
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
<hex>`, `hash <hex>`), or an ellipsis behind it (`<hex>…`), which is how this
archive writes a truncated digest. See `block_revs`.
The run leg is `<script> --repo R --rev REV`. A tool that takes no `--rev` The run leg is `<script> --repo R --rev REV`. A tool that takes no `--rev`
answers it with an argparse diagnosis and prints no census at all, so the leg answers it with an argparse diagnosis and prints no census at all, so the leg
@@ -142,6 +149,33 @@ CLAIM_EQ = re.compile(
r"(?P<unit>[A-Za-z_][A-Za-z0-9_]*)\s*[:=]\s*\**\s*(?P<num>[0-9][0-9,]*)") r"(?P<unit>[A-Za-z_][A-Za-z0-9_]*)\s*[:=]\s*\**\s*(?P<num>[0-9][0-9,]*)")
REV_LONG = re.compile(r"\b[0-9a-f]{7,40}\b") REV_LONG = re.compile(r"\b[0-9a-f]{7,40}\b")
# A hex run introduced as a DIGEST is not a revision, and reading it as one costs
# more than a wrong label: the block is filed under a revision that cannot exist,
# its leg fails, and then every claim in the block is adjudicated once per
# spurious rev beside the real ones. Measured 2026-10-16 at HEAD on
# `tools/queue_claim_check.py`, whose UNITS block quotes the queue's size as
# "/ sha256 `fbe51445a89f81ab…`": three claims emitted FOUR times, once per
# REV_LONG token, and two `git show <sha256-prefix>:WORK-QUEUE.md` leg failures.
# Two signals, both local to the token: a digest word in front of it (sha256,
# sha1, md5, digest, hash), or an ellipsis behind it -- the archive's own way of
# writing a truncated digest. A revision cited in the ordinary way carries
# neither, and a FULL sha256 is already invisible to REV_LONG: the run has no
# word boundary to bite on.
DIGEST_BEFORE = re.compile(
r"(?:sha[-\s]?256|sha[-\s]?1|md5|digest|hash)\s*[:=]?\s*[`'\"]?\s*$", re.I)
DIGEST_AFTER = re.compile(r"^[`'\"]?\s*(?:…|\.\.\.)")
def block_revs(text):
"""The revisions a block names -- hex runs read as digests excluded."""
out = set()
for m in REV_LONG.finditer(text):
if DIGEST_BEFORE.search(text[:m.start()]) or \
DIGEST_AFTER.match(text[m.end():]):
continue
out.add(m.group(0))
return sorted(out)
# Function words and units this tool does not adjudicate. `at` in "2026-10-15 at # Function words and units this tool does not adjudicate. `at` in "2026-10-15 at
# rev 9188dd3" is not a census quantity; neither is `B` (bytes differ by # rev 9188dd3" is not a census quantity; neither is `B` (bytes differ by
# revision by design, and "1,201,550 B" is a size, not a count). Stated so the # revision by design, and "1,201,550 B" is a size, not a count). Stated so the
@@ -440,7 +474,7 @@ def _fixture_checks():
lines = FAKE_DOC.split("\n") lines = FAKE_DOC.split("\n")
got, first_block, revs_seen = [], [], set() got, first_block, revs_seen = [], [], set()
for first, blk in blocks(lines, 1, len(lines)): for first, blk in blocks(lines, 1, len(lines)):
revs = sorted(set(REV_LONG.findall("\n".join(r for _, r in blk)))) revs = block_revs("\n".join(r for _, r in blk))
revs_seen |= set(revs) revs_seen |= set(revs)
for ln, val, unit, _tok, _pos in claims_in(blk): for ln, val, unit, _tok, _pos in claims_in(blk):
got.append((unit, val)) got.append((unit, val))
@@ -453,6 +487,16 @@ def _fixture_checks():
check("claims: `471 cannot be resolved` is not a claim", check("claims: `471 cannot be resolved` is not a claim",
any(u in ("cannot", "resolved") for u, _ in got), False) any(u in ("cannot", "resolved") for u, _ in got), False)
check("claims: the block's rev is read", "babdadc" in revs_seen, True) check("claims: the block's rev is read", "babdadc" in revs_seen, True)
# the digest rule, one signal per cell, and the negative control between
# them -- the text of each is the text of the defect, measured at HEAD
check("revs: `sha256 <hex>` is a digest, not the block's revision",
block_revs("queue 1,170,483 B / sha256 `c3b4a290dc64bd24`"), [])
check("revs: an ellipsis-truncated hex run is a digest, not a revision",
block_revs("at `28ca8e5` (1,193,681 B / `c3b4a290dc64bd24…`)"),
["28ca8e5"])
check("revs: an ordinarily cited revision is still the block's revision",
block_revs("Run 37 read (`29a4872`, queue 1,170,483 B) and `28ca8e5`"),
["28ca8e5", "29a4872"])
check("oracle: 471 matches", oracle(printed, clone, "UNRESOLVED", 471), check("oracle: 471 matches", oracle(printed, clone, "UNRESOLVED", 471),
("MATCHES", "")) ("MATCHES", ""))
check("oracle: 441 is stale", oracle(printed, clone, "UNRESOLVED", 441), check("oracle: 441 is stale", oracle(printed, clone, "UNRESOLVED", 441),
@@ -689,7 +733,7 @@ def check_tool(repo, script, name, cache, run=True, tmpdir=".", isolation=240):
blk_claims = [] blk_claims = []
for first, blk in blocks(lines, lo, hi): for first, blk in blocks(lines, lo, hi):
btext = "\n".join(raw for _, raw in blk) btext = "\n".join(raw for _, raw in blk)
revs = sorted(set(REV_LONG.findall(btext))) revs = block_revs(btext)
cl = claims_in(blk) cl = claims_in(blk)
if cl: if cl:
blk_claims.append((first, revs, cl, btext)) blk_claims.append((first, revs, cl, btext))