Files
troupe-standup/wizard-work/volt/q65_runner.py
T
volt 0e413bcb3d volt: Q65 runner -- grade ledger COVERAGE from the tree, and pin the rev it graded
unclassed() asks the tree at --rev, not the ledger, which verifier is named by nothing. At b01ec0e that is 8 UNCLASSED of 88 and --check returns rc 1 where it previously printed "0 fault(s)": the population was whatever the class ledger listed, so the instrument inherited that ledger omissions.

Also: emit() wrote "rev: HEAD". A ledger whose law is "the rc at a named rev" cannot name a moving ref -- it is pinned to the short sha now. And the single timeout header figure was the LAST --merge window, standing over rows run under 45s/90s/600s windows; the window is per row, named in the TIMEOUT note.

Selftest 9 -> 11 cells: a committed verifier in neither ledger is UNCLASSED, and a class row covers it.
2026-10-24 21:18:43 +00:00

503 lines
23 KiB
Python

#!/usr/bin/env python3
"""q65_runner.py -- the gate Q65 asked for: execute every committed verifier that
nothing else invokes, grade its exit code, and keep the grade in a ledger (Q65,
volt lane, 2026-10-24).
WHY THIS EXISTS
Q63 measured the class and Q65 holds the residual: at `6c26bc5`, 72 of the 87
committed `wizard-work/**/verify_*.py` were invoked by nothing. `unittest
discover` matches `test_*`, no `CHECK` globs `verify_*.py`, and no `tools/` leg
runs them. A verifier in that state is not a broken check, it is an unread
claim: its verdict is whatever its author last saw by hand, and if the whole
population were deleted the archive would not notice. Q65's done-when is one
recorded outcome per file -- invoked-and-graded, retired, or declared one-off.
The measure was a grep for an invocation-shaped line. This is the other half:
a gate that actually RUNS them, so the invocation is an execution rather than
a mention, and the outcome is an rc rather than a reading.
The alternative was 72 in-file declarations, one per file, each naming a grep
that shows the file is unwired. That records the defect 72 times; it does not
remove it. One runner grades the population, and a file added tomorrow is
graded by the next run without anyone remembering to add it.
WHAT IT MEASURES -- one row per population file, one verdict per row
PASS rc 0: the file ran and its checks held.
FAIL rc 1: the file ran and its checks did NOT hold. A finding, not a
bookkeeping error -- the file's claim is false at this tree.
REFUSED rc 2: the file declined to grade (its input is gone, its subject
is absent). Not a pass. The house contract will not let "could
not grade" read as "graded clean".
OTHER_RC rc 3 or above: a code the contract does not define.
TIMEOUT did not finish inside --timeout seconds.
ERROR the interpreter could not run the file at all (syntax error, an
exec failure): there is no rc to grade.
MISSING the path is in the ledger but not in the tree at --rev.
A row is the whole point. `--check` re-derives the population from `--rev`,
reads the ledger, and grades the bookkeeping: every population file has
exactly one row, every row is still in the tree, and every row's verdict is
consistent with the rc recorded beside it. rc 0 clean, rc 1 a fault, rc 2
could not grade -- the house three codes.
`--check` does NOT re-execute (that is `--emit`): a gate that takes minutes
is a gate that gets skipped. `--check --strict` makes any FAIL / REFUSED /
OTHER_RC / TIMEOUT / ERROR row fatal, for an operator who wants the runner to
be a red gate on the verifiers themselves rather than on the ledger.
WHY THE POPULATION IS A GLOB AND NOT A LIST OF NAMES
`q63_verify_scan.py` classes a file as wired when a committed line invokes it
by name. A globbed runner names no file, so the scan cannot see it -- and the
scan is right to be suspicious, because "a gate runs it" is exactly the claim
that is easy to make and hard to check. The evidence here is stronger than a
grep: the ledger rows are produced by execution, one per path discovered at
`--rev`, and `--check` fails if the two sets differ by a single path. A file
that the runner silently skipped would be a MISSING row, not an invisible
one. `q63_verify_scan.py` therefore accepts class `gate-run` for a path this
ledger carries a graded row for, and re-emits 0 `undeclared` rows.
WHAT IT DOES NOT DO
It does not sandbox. A verifier runs in its own directory, with bytecode
writing off, and the run prints any tracked file it dirtied (`git status`
before and after) -- but a verifier that wants to write will write. It does
not run two files at once: ordering is fixed and a shared temp name in one
file cannot make another look flaky. It does not re-run a flaky row; a row is
what one run said, and `--emit` again is how a second opinion is taken.
USAGE
python3 wizard-work/volt/q65_runner.py --emit # measure -> ledger
python3 wizard-work/volt/q65_runner.py --check # grade ledger, fast
python3 wizard-work/volt/q65_runner.py --why # per-row detail
python3 wizard-work/volt/q65_runner.py --class all --emit --timeout 120
"""
import argparse
import json
import os
import re
import subprocess
import sys
import tempfile
import time
HERE = os.path.dirname(os.path.abspath(__file__))
DEFAULT_REPO = os.path.abspath(os.path.join(HERE, "..", ".."))
DEFAULT_LEDGER = os.path.join(HERE, "q65_runner_baseline.tsv")
DEFAULT_Q63 = os.path.join(HERE, "q63_unwired_baseline.tsv")
VERIFIER_RE = re.compile(r"^verify_[^/]*\.py$")
PASS, FAIL, REFUSED, OTHER_RC, TIMEOUT, ERROR, MISSING = (
"PASS", "FAIL", "REFUSED", "OTHER_RC", "TIMEOUT", "ERROR", "MISSING")
VERDICTS = (PASS, FAIL, REFUSED, OTHER_RC, TIMEOUT, ERROR, MISSING)
# verdict <-> rc, the only pairs a ledger row may state
RC_OF = {PASS: "0", FAIL: "1", REFUSED: "2"}
VERDICT_OF_RC0 = PASS
FATAL_STRICT = (FAIL, REFUSED, OTHER_RC, TIMEOUT, ERROR, MISSING)
def lane_of(path):
"""`wizard-work/<lane>/...` -> lane; anything else -> '?'."""
parts = path.split("/")
return parts[1] if len(parts) > 2 and parts[0] == "wizard-work" else "?"
def git(repo, *args):
return subprocess.run(["git"] + list(args), cwd=repo, capture_output=True,
text=True)
def rev_files(repo, rev):
"""Every tracked path at `rev` whose basename is verify_*.py."""
r = git(repo, "ls-tree", "-r", "--name-only", rev)
if r.returncode != 0:
raise SystemExit("q65_runner: cannot read tree at %s: %s"
% (rev, (r.stderr or "").strip()[:200]))
return sorted(p for p in r.stdout.splitlines() if VERIFIER_RE.match(
os.path.basename(p)))
def read_classes(path):
"""path -> class, from a <class>\\t<path> ledger with # comments."""
out = {}
with open(path, encoding="utf-8") as fh:
for line in fh:
line = line.rstrip("\n")
if not line or line.startswith("#"):
continue
parts = line.split("\t")
if len(parts) < 2:
raise SystemExit("q65_runner: malformed row in %s: %r" % (path, line))
out[parts[1]] = parts[0]
return out
def population(repo, rev, classes_path, want):
tracked = set(rev_files(repo, rev))
if want == "all":
return {p: "ungated" for p in tracked}
cls = read_classes(classes_path)
want = [want] if isinstance(want, str) else want
return {p: c for p, c in cls.items() if c in want and p in tracked}
def unclassed(repo, rev, classes_path, ledger_paths):
"""Committed verifiers that NEITHER ledger names -- the coverage rule.
The population is whatever the class ledger lists, so a committed verifier
absent from that ledger is invisible, and `--check` used to print
`0 fault(s)` over it: at `b01ec0e` that was 8 of 88 committed verifiers
(6 genuinely unwired, 2 invoked from a committed gate). The ledger is the
side of the comparison that can omit itself; the tree at `--rev` cannot.
Measured here so the omission is a fault rather than a silence.
"""
cls = read_classes(classes_path)
return sorted(p for p in rev_files(repo, rev)
if p not in cls and p not in ledger_paths)
def run_one(repo, path, timeout, cwd_mode):
"""Execute one verifier. Returns a row dict; never raises."""
full = os.path.join(repo, path)
if not os.path.exists(full):
return {"path": path, "verdict": MISSING, "rc": None, "seconds": 0.0,
"note": "not in worktree"}
cwd = repo if cwd_mode == "repo" else os.path.dirname(full)
target = os.path.relpath(full, cwd)
env = dict(os.environ, PYTHONDONTWRITEBYTECODE="1", PYTHONUNBUFFERED="1")
env.pop("PYTHONOPTIMIZE", None)
t0 = time.time()
try:
r = subprocess.run([sys.executable, "-B", target],
cwd=cwd, env=env, capture_output=True, timeout=timeout,
text=True, errors="replace")
except subprocess.TimeoutExpired:
return {"path": path, "verdict": TIMEOUT, "rc": None,
"seconds": round(time.time() - t0, 2),
"note": "no finish in %ss" % timeout}
secs = round(time.time() - t0, 2)
rc = r.returncode
if rc == 0:
verdict = PASS
elif rc == 1:
verdict = FAIL
elif rc == 2:
verdict = REFUSED
else:
verdict = OTHER_RC if rc > 2 else ERROR
note = ""
tail = (r.stderr or "").strip().splitlines()
if verdict is not PASS and tail:
note = tail[-1][:160]
return {"path": path, "verdict": verdict, "rc": rc, "seconds": secs,
"note": note}
def emit(rows, path, repo, rev, timeout):
# The rev is PINNED to a sha, not written as the word `HEAD`: a ledger whose
# law is "the rc at a named rev" cannot name a moving ref, because HEAD one
# commit later is a different population and the same rows read as current.
sha = (git(repo, "rev-parse", "--short", rev).stdout or rev).strip() or rev
lines = [
"# Q65 runner ledger -- one row per committed verify_*.py the runner",
"# executes, with the exit code it actually returned at the stated rev.",
"# row: <class>\\t<path>\\t<verdict>\\t<rc>\\t<seconds>\\t<note>",
"# verdicts: PASS rc0 / FAIL rc1 / REFUSED rc2 / OTHER_RC >2 /",
"# TIMEOUT, ERROR, MISSING (no rc to grade).",
"# class is the Q63 ledger class the file came from at emit time.",
"# A FAIL row is a finding held open, not a tolerated pass: --check",
"# grades the bookkeeping, --check --strict makes any non-PASS fatal.",
"# timeout is PER ROW: a TIMEOUT row names its own window in the note, so",
"# a --merge cannot leave a stale single figure standing for rows run",
"# under a different one (the defect this line replaces).",
"# Regenerate: q65_runner.py --emit",
"# rev: %s rows: %d emitted: q65_runner.py" % (sha, len(rows)),
]
for r in sorted(rows, key=lambda x: x["path"]):
lines.append("\t".join([r.get("class", "ungated"), r["path"], r["verdict"],
"" if r["rc"] is None else str(r["rc"]),
"%.2f" % (float(r["seconds"]) if r.get("seconds") else 0.0),
(r.get("note") or "").replace("\t", " ")]))
tmp = path + ".tmp"
with open(tmp, "w", encoding="utf-8") as fh:
fh.write("\n".join(lines) + "\n")
os.replace(tmp, path)
return path
def read_ledger(path):
"""(rows, error). rows keep every column; error is a string for rc 2."""
if not os.path.exists(path):
return [], "no ledger at %s (run --emit first)" % path
rows = []
with open(path, encoding="utf-8") as fh:
for n, line in enumerate(fh, 1):
line = line.rstrip("\n")
if not line or line.startswith("#"):
continue
parts = line.split("\t")
if len(parts) < 4:
return [], "malformed row %d in %s: %r" % (n, path, line)
if parts[2] not in VERDICTS:
return [], "unknown verdict %r at row %d in %s" % (parts[2], n, path)
rows.append({"class": parts[0], "path": parts[1], "verdict": parts[2],
"rc": parts[3] if parts[3] else None,
"seconds": parts[4] if len(parts) > 4 else "",
"note": parts[5] if len(parts) > 5 else ""})
return rows, None
def rc_consistent(row):
"""The verdict must match the rc recorded beside it. Returns a complaint."""
v, rc = row["verdict"], row["rc"]
if v in RC_OF:
if rc != RC_OF[v]:
return "verdict %s with rc %s (expected %s)" % (v, rc, RC_OF[v])
return None
if v is OTHER_RC:
if rc is None or not rc.isdigit() or int(rc) <= 2:
return "OTHER_RC with rc %r" % rc
return None
if rc is not None:
return "%s carries rc %s (a code to grade)" % (v, rc)
return None
def check(repo, rev, classes_path, want, ledger_path, strict):
"""Grade the ledger against the tree. rc 0 clean / 1 fault / 2 could not grade."""
try:
pop = population(repo, rev, classes_path, want)
except SystemExit as exc:
print("q65_runner: %s" % exc)
return 2
rows, err = read_ledger(ledger_path)
if err:
print("q65_runner: %s" % err)
return 2
faults = []
seen = {}
for r in rows:
seen[r["path"]] = seen.get(r["path"], 0) + 1
why = rc_consistent(r)
if why:
faults.append("INCONSISTENT %s: %s" % (r["path"], why))
for p, n in sorted(seen.items()):
if n > 1:
faults.append("DUPLICATE %s: %d rows" % (p, n))
audit = [] if want == "all" else unclassed(repo, rev, classes_path, set(seen))
for p in audit:
faults.append("UNCLASSED %s: committed verify_*.py at %s, in no class row "
"in %s and no ledger row"
% (p, rev, os.path.basename(classes_path)))
ungraded = sorted(set(pop) - set(seen))
stale = sorted(set(seen) - set(pop))
for p in ungraded:
faults.append("UNGRADED %s: in the population at %s, no ledger row" % (p, rev))
for p in stale:
if not os.path.exists(os.path.join(repo, p)):
faults.append("STALE %s: ledger row, file not in the tree" % p)
elif strict:
faults.append("OUTSIDE %s: ledger row, not in the --class population" % p)
counts = {}
for r in rows:
if r["path"] in pop:
counts[r["verdict"]] = counts.get(r["verdict"], 0) + 1
print("q65_runner -- rev %s, %d population file(s), %d ledger row(s)"
% (rev, len(pop), len(rows)))
graded = sum(1 for r in rows if r["path"] in pop and r["rc"] is not None
and rc_consistent(r) is None)
print(" rows at emit : %d (%s)" % (
len(rows),
", ".join("%s %d" % (k, counts[k]) for k in VERDICTS if counts.get(k))))
print(" graded (has rc): %d of %d" % (graded, len(pop)))
print(" tree audit : %d unclassed of %d committed verifier(s)%s"
% (len(audit), len(pop) if want == "all" else len(rev_files(repo, rev)),
"" if not audit else " <-- named by neither ledger"))
if strict:
for r in rows:
if r["verdict"] in FATAL_STRICT:
faults.append("%s %s rc %s %s" % (r["verdict"], r["path"], r["rc"],
r["note"][:80]))
for f in faults:
print(" " + f)
print("q65: %d fault(s)" % len(faults))
return 1 if faults else 0
def selftest():
"""Small cells for the two rules that can silently pass: the verdict/rc
pairing and the population coverage."""
cells = []
def cell(n, ok):
cells.append((n, bool(ok)))
good = {"path": "a/b.py", "verdict": PASS, "rc": "0", "note": ""}
cell("c1 PASS rc 0 is consistent", rc_consistent(good) is None)
cell("c2 PASS rc 1 is a fault",
"rc 1" in (rc_consistent({"path": "a/b.py", "verdict": PASS, "rc": "1",
"note": ""}) or ""))
cell("c3 TIMEOUT carrying an rc is a fault",
rc_consistent({"path": "x", "verdict": TIMEOUT, "rc": "1", "note": ""}) is not None)
cell("c4 OTHER_RC rc 3 is consistent",
rc_consistent({"path": "x", "verdict": OTHER_RC, "rc": "3", "note": ""}) is None)
cell("c5 OTHER_RC rc 7 is consistent",
rc_consistent({"path": "x", "verdict": OTHER_RC, "rc": "7", "note": ""}) is None)
cell("c6 OTHER_RC rc 2 is a fault (that is REFUSED)",
rc_consistent({"path": "x", "verdict": OTHER_RC, "rc": "2", "note": ""}) is not None)
tmp = tempfile.mkdtemp(prefix="q65-selftest-")
led = os.path.join(tmp, "l.tsv")
with open(led, "w", encoding="utf-8") as fh:
fh.write("# c\n")
fh.write("ungated\twizard-work/x/verify_a.py\tPASS\t0\t0.01\t\n")
fh.write("ungated\twizard-work/x/verify_b.py\tFAIL\t1\t0.02\tboom\n")
rows, err = read_ledger(led)
cell("c7 ledger round-trips two rows", err is None and len(rows) == 2)
cell("c8 a missing ledger is an error, not an empty pass",
read_ledger(os.path.join(tmp, "nope.tsv"))[1] is not None)
bad = os.path.join(tmp, "bad.tsv")
with open(bad, "w", encoding="utf-8") as fh:
fh.write("ungated\twizard-work/x/verify_a.py\tPASSED\t0\t0.01\t\n")
cell("c9 an unknown verdict refuses the ledger",
read_ledger(bad)[1] is not None)
# c10/c11: the coverage rule. A committed verifier named by neither ledger is
# a fault, even though the ledger that would grade it is the one it is
# missing from -- that is how 8 of 88 stood invisible at `b01ec0e`.
import subprocess as _sp
tree = tempfile.mkdtemp(prefix="q65-tree-")
env = dict(os.environ, GIT_AUTHOR_NAME="t", GIT_AUTHOR_EMAIL="t@t",
GIT_COMMITTER_NAME="t", GIT_COMMITTER_EMAIL="t@t")
_sp.run(["git", "init", "-q", tree], check=True, env=env)
lane = os.path.join(tree, "wizard-work", "x")
os.makedirs(lane)
with open(os.path.join(lane, "verify_orphan.py"), "w", encoding="utf-8") as fh:
fh.write(chr(10).join(("import sys", "sys.exit(0)")) + chr(10))
_sp.run(["git", "-C", tree, "add", "-A"], check=True, env=env)
_sp.run(["git", "-C", tree, "commit", "-qm", "c"], check=True, env=env)
census = os.path.join(tree, "census.tsv")
open(census, "w", encoding="utf-8").close()
cell("c10 a committed verifier in neither ledger is UNCLASSED",
unclassed(tree, "HEAD", census, set()) == ["wizard-work/x/verify_orphan.py"])
with open(census, "w", encoding="utf-8") as fh:
fh.write(chr(9).join(("undeclared", "wizard-work/x/verify_orphan.py",
"no run")) + chr(10))
cell("c11 a class row covers the same file",
unclassed(tree, "HEAD", census, set()) == [])
passed = sum(1 for _, ok in cells if ok)
for n, ok in cells:
print("%-4s %s" % ("ok" if ok else "FAIL", n))
print("selftest: %d cells, %d passed, %d failed" % (len(cells), passed,
len(cells) - passed))
return 0 if passed == len(cells) else 1
def main():
ap = argparse.ArgumentParser(
description="run every committed verify_*.py nothing else invokes, grade its rc")
ap.add_argument("--repo", default=DEFAULT_REPO)
ap.add_argument("--rev", default="HEAD")
ap.add_argument("--q63", default=DEFAULT_Q63, help="the class ledger to read")
ap.add_argument("--class", dest="cls", default="undeclared",
help="undeclared (Q65 residual) | declared-one-off | all")
ap.add_argument("--lane", help="with --emit: only wizard-work/<lane>/ rows")
ap.add_argument("--only", help="with --emit: only paths containing this substring")
ap.add_argument("--merge", action="store_true",
help="with --emit: keep rows for paths this run did not touch")
ap.add_argument("--ledger", default=DEFAULT_LEDGER)
ap.add_argument("--timeout", type=float, default=60.0)
ap.add_argument("--cwd", choices=("dir", "repo"), default="dir")
ap.add_argument("--emit", action="store_true", help="execute and write the ledger")
ap.add_argument("--check", action="store_true", help="grade the ledger, no execution")
ap.add_argument("--strict", action="store_true",
help="with --check: any non-PASS row is a fault")
ap.add_argument("--why", action="store_true", help="per-row detail")
ap.add_argument("--limit", type=int, default=0, help="stop after N rows (probe runs)")
ap.add_argument("--json")
ap.add_argument("--selftest", action="store_true")
args = ap.parse_args()
if args.selftest:
return selftest()
if not (args.emit or args.check or args.why):
ap.print_help()
return 2
try:
pop = population(args.repo, args.rev, args.q63, args.cls)
except SystemExit as exc:
print("q65_runner: %s" % exc)
return 2
if not pop:
print("q65_runner: population is empty for --class %s at %s"
% (args.cls, args.rev))
return 2
if args.emit:
before = git(args.repo, "status", "--porcelain").stdout
rows = []
todo = sorted(pop)
if args.lane:
todo = [p for p in todo if lane_of(p) == args.lane]
if args.only:
todo = [p for p in todo if args.only in p]
if args.limit:
todo = todo[:args.limit]
if args.merge and os.path.exists(args.ledger):
have, err = read_ledger(args.ledger)
if err:
print("q65_runner: %s" % err)
return 2
rows = [r for r in have if r["path"] not in set(todo)]
t0 = time.time()
for p in todo:
r = run_one(args.repo, p, args.timeout, args.cwd)
r["class"] = pop[p]
rows.append(r)
# rewrite after every row: an interrupted sweep keeps the rows it
# finished, and --merge resumes from them
emit(rows, args.ledger, args.repo, args.rev, args.timeout)
print(" %-8s %6.2fs %s" % (r["verdict"], r["seconds"], p), flush=True)
out = emit(rows, args.ledger, args.repo, args.rev, args.timeout)
after = git(args.repo, "status", "--porcelain").stdout
led_rel = os.path.relpath(args.ledger, args.repo)
dirtied = sorted(d for d in set(after.splitlines()) - set(before.splitlines())
if led_rel not in d)
counts = {}
for r in rows:
counts[r["verdict"]] = counts.get(r["verdict"], 0) + 1
print("q65_runner: %d row(s) in %.1fs -> %s" % (len(rows), time.time() - t0, out))
print(" " + ", ".join("%s %d" % (k, counts[k]) for k in VERDICTS if counts.get(k)))
if dirtied:
print(" WARNING: the run changed the worktree:")
for d in dirtied:
print(" " + d)
if args.json:
with open(args.json, "w", encoding="utf-8") as fh:
json.dump({"rev": args.rev, "rows": rows, "counts": counts,
"dirtied": dirtied}, fh, indent=1)
return 0 if not dirtied else 0
if args.check:
return check(args.repo, args.rev, args.q63, args.cls, args.ledger, args.strict)
# --why on its own: print the ledger, grouped by lane
rows, err = read_ledger(args.ledger)
if err:
print("q65_runner: %s" % err)
return 2
by_lane = {}
for r in rows:
lane = r["path"].split("/")[1] if r["path"].startswith("wizard-work/") else "?"
by_lane.setdefault(lane, []).append(r)
for lane in sorted(by_lane):
rs = by_lane[lane]
mark = ",".join(sorted({x["verdict"] for x in rs}))
print("%-9s %3d row(s) %s" % (lane, len(rs), mark))
for r in sorted(rs, key=lambda x: x["path"]):
if r["verdict"] is not PASS:
print(" %-8s rc %-4s %s %s" % (r["verdict"], r["rc"] or "-",
r["path"], r["note"][:90]))
return 0
if __name__ == "__main__":
sys.exit(main())