From ecdaecd7cb2c0805fffbb17725414449e8980a34 Mon Sep 17 00:00:00 2001 From: volt Date: Sun, 18 Oct 2026 10:09:45 +0000 Subject: [PATCH] tools (volt): doc_census_check -- a cached vocabulary gives the fast mode its teeth back Reply 22 ruled the asymmetry a defect and asked that `--fail` never return 1 on a split it could not verify. 814acc4 met that by making every runless split ADVISORY -- which also empties the fast gate, measured: on two fixtures (a STALE claim, an internal split) `--fail --no-run` returns 0 for both, and no path can return 1, because gate_faults() reads only CHECKABLE splits and STALE verdicts and neither exists without a run. So the fast mode gets the oracle it can have. `--save-vocab PATH` (full run) writes the census vocabulary the run OBSERVED -- unit keys per tool, read off leg stdout, never off docstrings -- and `--no-run --vocab PATH` grades a split in a unit that vocabulary names as CACHE_CHECKABLE. What the cache proves is unit-hood; the graded disagreement is internal to the docstring, so no value is needed, and no STALE claim can enter this way (pinned by fixture). Measured at fca3f2e, tools/citation_resolve_check.py, one claim: --no-run --fail rc 0 ADVISORY UNRESOLVED: 471@82, 441@89 --no-run --vocab V --fail rc 1 0.205s, SPLIT ... (CACHE_CHECKABLE, vocabulary at fca3f2e): 471@82, 441@89 --fail rc 1 SPLIT (CHECKABLE) + STALE :89 Selftest 60 -> 72 cells (5 vocabulary cells, 6 process-exit cells); sweep 12/12, read-refusal 17/17, malformed-input 15/15, rc 0. rc 2 covers an unreadable --vocab and --save-vocab without a run. --- tools/doc_census_check.py | 202 +++++++++++++++++++++++++++++++++----- 1 file changed, 179 insertions(+), 23 deletions(-) diff --git a/tools/doc_census_check.py b/tools/doc_census_check.py index 9ccfcdf..4ff2185 100644 --- a/tools/doc_census_check.py +++ b/tools/doc_census_check.py @@ -152,9 +152,12 @@ FAILING ON THE CLASS IT CATCHES (`--fail`) The code means: 0 ran, and graded no fault (every claim adjudicated agrees) - 1 ran, and graded a fault (a VERIFIED INTERNAL SPLIT, or STALE) + 1 ran, and graded a fault (an INTERNAL SPLIT whose unit a leg -- + or a cached vocabulary -- verified, + or a STALE claim) 2 could not grade (usage, a --tools filter that matches - nothing tracked, unreadable input) + nothing tracked, an unreadable + --vocab, --save-vocab without a run) THE TWO MODES GRADE THE SAME CLASS. `--no-run` runs nothing, so it has no oracle and cannot prove any split is a census unit: it prints every split as @@ -167,6 +170,23 @@ FAILING ON THE CLASS IT CATCHES (`--fail`) run prints `UNRESOLVED 471` -- plus the STALE claim at :89. The teeth are kept where they can be verified, which is the only place they mean anything. + A CACHED VOCABULARY PUTS THE TEETH BACK IN THE FAST MODE, without asking it + to invent an oracle. `--save-vocab PATH` (full run) writes the census + vocabulary the run OBSERVED -- every unit key some leg printed, per tool -- + and `--no-run --vocab PATH` grades a split in a unit that vocabulary names as + CACHE_CHECKABLE. What the cache proves is narrow and enough: the unit is a + census unit for that tool (a leg printed it at ``), and the + disagreement being graded is INTERNAL to the docstring, so the leg never has + to supply the value. What it cannot prove is a value, so a STALE claim is + still impossible without a run, and a unit the vocabulary does not name stays + ADVISORY -- the 39/12/1 `ACS-only` class is still never spent. Measured + 2026-10-18 at 814acc4 on a stub whose docstring splits 471/441 over a census + printing 471: `--no-run --fail` rc 0, `--no-run --vocab V --fail` rc 1, + `--fail` rc 1. The vocabulary's rev is printed with every CACHE_CHECKABLE + row, because a reader is entitled to know how old the census behind the + label is; it is NOT required to equal the revision being checked (unit-hood + is a property of the unit word, and the graded disagreement is internal). + rc 2 keeps its meaning from the tools/ contract (6ee8fce) and is decided before the gate runs, so "could not grade" never reads as "graded clean" or as "graded a fault". A LEG FAILURE (RUN_FAILED) is reported in its own column @@ -176,7 +196,8 @@ FAILING ON THE CLASS IT CATCHES (`--fail`) USAGE python3 doc_census_check.py [--repo DIR] [--rev HEAD] [--tools NAME...] - [--json OUT] [--no-run] [--fail] [--selftest] # selftest: 60 cells, one per claim class + [--json OUT] [--fail] [--no-run] [--vocab PATH] [--save-vocab PATH] + [--selftest] # selftest: 72 cells, one per claim class """ import argparse import ast @@ -806,6 +827,24 @@ def _fixture_checks(): check("mode: --fail cannot fail on a split it could not verify", gate_rc([rep_norun], True), 0) check("mode: and still fails on the verified one", gate_rc([rep_run], True), 1) + # a cached vocabulary grades unit-hood without a run -- and only when it + # names the unit, which is the whole difference between a proof and a + # guess (reply 22, option 2) + check("vocab: a cached census unit is recognised", + vocab_has("UNRESOLVED", {"unresolved", "citations"}), True) + check("vocab: a unit the vocabulary does not name is not", + vocab_has("WOMBATS", {"unresolved"}), False) + rep_cached = check_tool(td, split_stub, "stub_split.py", {}, run=False, + tmpdir=td, vocab={"unresolved"}, vocab_rev="abc1234") + check("vocab: the same runless split grades as CACHE_CHECKABLE", + [(s["unit"], s["how"], s["oracle_rev"]) for s in rep_cached["splits"]], + [("UNRESOLVED", "CACHE_CHECKABLE", "abc1234")]) + check("vocab: and the gate spends it", gate_rc([rep_cached], True), 1) + rep_other = check_tool(td, split_stub, "stub_split.py", {}, run=False, + tmpdir=td, vocab={"wombats"}, vocab_rev="abc1234") + check("vocab: a vocabulary naming another unit leaves it advisory", + ([s["unit"] for s in rep_other["splits"]], gate_rc([rep_other], True)), + ([], 0)) with tempfile.TemporaryDirectory(prefix="doccensus-gate-") as td: os.makedirs(os.path.join(td, "tools")) @@ -841,6 +880,41 @@ def _fixture_checks(): run_gate("--tools", "tools/stub_split.py", "--no-run", "--fail"), 0) check("gate e2e: --fail with the run grades that same split (rc 1)", run_gate("--tools", "tools/stub_split.py", "--fail"), 1) + # the process exit code through the vocabulary, both directions, plus + # the two ways a vocabulary can fail to be usable at all + vpath = os.path.join(td, "vocab-names-unit.json") + with open(vpath, "w") as fh: + json.dump({"rev": "abc1234", + "tools": {"tools/stub_split.py": ["unresolved"]}}, fh) + check("gate e2e: --no-run --vocab grades a split the vocabulary names (rc 1)", + run_gate("--tools", "tools/stub_split.py", "--no-run", + "--vocab", vpath, "--fail"), 1) + vother = os.path.join(td, "vocab-other-unit.json") + with open(vother, "w") as fh: + json.dump({"rev": "abc1234", + "tools": {"tools/stub_split.py": ["wombats"]}}, fh) + check("gate e2e: a vocabulary that does not name the unit leaves rc 0", + run_gate("--tools", "tools/stub_split.py", "--no-run", + "--vocab", vother, "--fail"), 0) + check("gate e2e: an unreadable --vocab is rc 2, not 0 and not 1", + run_gate("--tools", "tools/stub_split.py", "--no-run", + "--vocab", os.path.join(td, "nope.json"), "--fail"), 2) + check("gate e2e: --save-vocab without a run is rc 2", + run_gate("--tools", "tools/stub_split.py", "--no-run", + "--save-vocab", os.path.join(td, "v0.json"), "--fail"), 2) + # the round trip a gate runner would actually run: a full run writes the + # vocabulary it observed, the fast mode reads it back and still fails + vrt = os.path.join(td, "vocab-roundtrip.json") + check("gate e2e: a full run writes the vocabulary it observed (rc 1)", + run_gate("--tools", "tools/stub_split.py", "--save-vocab", vrt, + "--fail"), 1) + with open(vrt) as fh: + check("gate e2e: the written vocabulary names the unit the leg printed", + sorted(json.load(fh)["tools"].get("tools/stub_split.py", [])), + ["unresolved"]) + check("gate e2e: the round trip fails the fast mode on the same split", + run_gate("--tools", "tools/stub_split.py", "--no-run", + "--vocab", vrt, "--fail"), 1) print(f"selftest: {cells[0]} cells, {cells[0] - bad[0]} passed, {bad[0]} failed") return 0 if ok else 1 @@ -920,7 +994,9 @@ def unit_scope(unit, legs, no_run): DESIGN (`citations` prints once under EXPLICIT and once under BARE), so a docstring that mirrors both is correct and is reported as such, not flagged. A unit no leg prints is not a census unit here. Without a run, only - uppercase census labels are checked. + uppercase census labels are checked -- and a cached vocabulary (see + vocab_has) can widen that, which is why the cache decision is taken in + check_tool, not here: this function answers about the legs it was given. """ if no_run or not legs: return ("LABEL_ONLY" if is_label(unit) else "UNCHECKED_NO_ORACLE") @@ -940,7 +1016,19 @@ def unit_scope(unit, legs, no_run): return "NOT_A_CENSUS_UNIT" -def check_tool(repo, script, name, cache, run=True, tmpdir=".", isolation=240): +def vocab_has(unit, vocab): + """True when a cached census vocabulary names this unit word. + + The vocabulary is a set of the unit KEYS a full run observed (`unresolved`, + `citations`, `acs_only` ...), and `keys_for` is the same matcher the oracle + uses -- so a cache hit means exactly "a leg printed this unit under this + key", no letter of the rule re-invented for the cache. + """ + return bool(keys_for(unit, {u: [0] for u in (vocab or ())})) + + +def check_tool(repo, script, name, cache, run=True, tmpdir=".", isolation=240, + vocab=None, vocab_rev=None): text = open(script, encoding="utf-8", errors="replace").read() span = docstring_span(script, text) if span is None: @@ -1015,21 +1103,35 @@ def check_tool(repo, script, name, cache, run=True, tmpdir=".", isolation=240): rev_for_oracle = (items[0][0], scope[0] if len(scope) == 1 else "HEAD") why = ("SELF_NOT_RUN" if is_self and run else unit_scope(unit, legs_for(*rev_for_oracle), not run)) + oracle_rev = None + # A CACHED VOCABULARY WIDENS WHAT A RUNLESS MODE MAY GRADE, and only in + # the one direction it can prove: a unit some leg printed at the + # vocabulary's rev is a census unit, and the disagreement here is + # internal to this docstring, so the leg's VALUE is never needed. It is + # consulted only when nothing ran -- with a run, a unit the live leg + # does not print is evidence AGAINST census-hood and the cache must not + # overrule it. A STALE claim never enters through here: `why` only + # decides splits. + if why in ("LABEL_ONLY", "UNCHECKED_NO_ORACLE") and not run \ + and vocab_has(unit, vocab): + why, oracle_rev = "CACHE_CHECKABLE", vocab_rev row = {"unit": unit, "revs": list(scope), "values": sorted(vals), - "how": why, "rows": [{"line": ln, "value": v, "block": b} - for b, ln, v, _ in items]} + "how": why, "oracle_rev": oracle_rev, + "rows": [{"line": ln, "value": v, "block": b} + for b, ln, v, _ in items]} # THE ONLY DOOR INTO `splits`. A split is graded only when a leg ran and - # printed its unit with exactly one value (CHECKABLE): then the docstring - # and the output are talking about one number and a disagreement is a - # defect the gate can verify. Every other reason -- no oracle at all - # (LABEL_ONLY, --no-run), a unit no leg prints (NOT_A_CENSUS_UNIT), a - # unit the tool itself prints per section (SECTION_SPLIT_BY_TOOL), this - # tool reading itself (SELF_NOT_RUN) -- is reported with its values and - # never reaches the gate. Decided 2026-10-18 (reply 22) after - # `--fail --no-run` returned 1 on the 471/441 split at fca3f2e while the - # full run returned 0 for the same units it could not see: rc 1 must - # never mean "I could not check this". - if why != "CHECKABLE": + # printed its unit with exactly one value (CHECKABLE), or when a cached + # vocabulary proves the unit is a census unit and no run happened + # (CACHE_CHECKABLE): then the docstring and the census are talking about + # one number and a disagreement is a defect the gate can verify. Every + # other reason -- no oracle at all (LABEL_ONLY, --no-run), a unit no leg + # prints (NOT_A_CENSUS_UNIT), a unit the tool itself prints per section + # (SECTION_SPLIT_BY_TOOL), this tool reading itself (SELF_NOT_RUN) -- is + # reported with its values and never reaches the gate. Decided 2026-10-18 + # (reply 22) after `--fail --no-run` returned 1 on the 471/441 split at + # fca3f2e while the full run returned 0 for the same units it could not + # see: rc 1 must never mean "I could not check this". + if why not in ("CHECKABLE", "CACHE_CHECKABLE"): unchecked[why] += 1 advisory.append(row) continue @@ -1157,12 +1259,40 @@ def main(): "VERIFIED (CHECKABLE) or a STALE claim; unverified " "splits and leg failures are reported, never spent as " "defects (rc 2 stays 'could not grade')") + ap.add_argument("--vocab", metavar="PATH", + help="a census vocabulary written by --save-vocab: under " + "--no-run, a split in a unit the vocabulary names is " + "GRADED (CACHE_CHECKABLE) instead of labelled. The " + "vocabulary proves unit-hood, never a value, so no " + "STALE claim can enter this way") + ap.add_argument("--save-vocab", metavar="PATH", + help="write the census vocabulary THIS RUN observed (unit " + "keys per tool) for a later --no-run --vocab; needs " + "the run, so it is refused with --no-run (rc 2)") ap.add_argument("--selftest", action="store_true") a = ap.parse_args() if a.selftest: return _fixture_checks() + if a.save_vocab and a.no_run: + print("cannot save a vocabulary: --save-vocab needs the census a run " + "prints, and --no-run runs nothing", file=sys.stderr) + return 2 + vocab, vocab_rev = {}, None + if a.vocab: + try: + with open(a.vocab, encoding="utf-8") as fh: + vdoc = json.load(fh) + vocab = {t: set(u) for t, u in (vdoc.get("tools") or {}).items()} + vocab_rev = vdoc.get("rev") + except (OSError, ValueError, AttributeError, TypeError) as exc: + print(f"cannot grade: --vocab {a.vocab}: {exc}", file=sys.stderr) + return 2 + if not a.no_run: + print("note: --vocab is read only by the runless mode; a live leg is " + "the oracle when the tool runs", file=sys.stderr) + store = git(a.repo, "ls-tree", "-r", "--name-only", a.rev) tree = {p for p in store.decode("utf-8", "replace").split("\n") if p} tools, missing = resolve_tools(a.tools, tree) @@ -1186,7 +1316,8 @@ def main(): fh.write(src) try: rep = check_tool(a.repo, local, t, cache, run=not a.no_run, - tmpdir=tmpdir) + tmpdir=tmpdir, vocab=vocab.get(t), + vocab_rev=vocab_rev) except subprocess.TimeoutExpired: rep = {"tool": t, "splits": [], "blocks_with_claims": 0, "verdicts": [{"file": t, "verdict": "TIMEOUT", "rev": "-", @@ -1196,6 +1327,10 @@ def main(): print(f"tools scanned at {a.rev}: {len(reports)} " f"({'internal-split only, label vocabulary' if a.no_run else 'docstring vs printed census'})") + if a.vocab: + print(f"vocabulary: {a.vocab} -- census at {vocab_rev}, " + f"{len(vocab)} tool(s), {sum(len(v) for v in vocab.values())} unit(s)" + + ("" if a.no_run else " (not read: a live leg is the oracle)")) tot = collections.Counter() for rep in reports: bad = rep["failures"] + [v for v in rep["verdicts"] if v["verdict"] == "STALE"] @@ -1207,7 +1342,9 @@ def main(): for s in rep["splits"]: vals = ", ".join(f"{r['value']}@{r['line']}" for r in s["rows"]) scope = ",".join(s["revs"]) or "no rev named" - print(f" SPLIT {s['unit']} [{scope}] ({s['how']}): {vals}") + prov = (f", vocabulary at {s['oracle_rev']}" + if s.get("oracle_rev") else "") + print(f" SPLIT {s['unit']} [{scope}] ({s['how']}{prov}): {vals}") for k, c in sorted(rep["unchecked"].items()): print(f" (not flagged: {c} split(s) in units {k})") # A --no-run split is the one advisory class a reader of the fast mode @@ -1235,17 +1372,36 @@ def main(): with open(a.json, "w") as fh: json.dump({"rev": a.rev, "repo": a.repo, "reports": reports}, fh, indent=1) print(f"wrote {a.json}") + if a.save_vocab: + # The vocabulary is READ OFF THE RUN, not off the docstrings: only the + # keys `parse_printed` saw in a leg's stdout count as census units, so a + # tool whose leg failed contributes nothing and cannot hand the fast + # mode a vocabulary it never printed. + units = collections.defaultdict(set) + for (name, rev), entry in cache.items(): + if rev == "" or not entry[0]: + continue + units[name].update(entry[0][0].keys()) + with open(a.save_vocab, "w", encoding="utf-8") as fh: + json.dump({"rev": a.rev, "repo": a.repo, + "tools": {k: sorted(v) for k, v in units.items()}}, + fh, indent=1, sort_keys=True) + print(f"wrote vocabulary {a.save_vocab}: {len(units)} tool(s), " + f"{sum(len(v) for v in units.values())} unit(s) at {a.rev}") if a.fail: nsplit, nstale = gate_faults(reports) if nsplit or nstale: print(f"\nFAIL: {nsplit} INTERNAL SPLIT + {nstale} STALE claim(s) " f"at {a.rev}" - + (" (internal-split only: --no-run)" if a.no_run else "")) + + (" (graded on a cached census vocabulary: --no-run --vocab)" + if a.no_run else "")) return 1 print(f"\nOK: 0 INTERNAL SPLIT, 0 STALE claim(s) at {a.rev}" + (" (no oracle in this mode: nothing was verified, so nothing is " - "graded -- ADVISORY rows above are labels, not faults)" - if a.no_run else "")) + "graded -- ADVISORY rows above are labels, not faults" + + ("; the vocabulary graded any split in a unit it names)" + if a.vocab else ")") + if a.no_run else "")) return gate_rc(reports, a.fail)