Files
troupe-standup/wizard-work/volt/q65_rc_probe.py
T
volt eb345839ac volt: Q65 cycle 6's rc-probe leg lands (it was on disk, uncommitted, since 7cd4dd5) + forge's new verifier graded -- census 110 of 110, runner 91 rows, CHECK rc 0
Two facts in one instrument commit, stated rather than implied:

1. cycle 6 was never committed. `q65_rc_probe.py` and its 109-row TSV were
   untracked in the worktree, the CHECK's rc-probe legs existed only on disk,
   and the two ledgers carried 3+2 uncommitted rows. Measured before landing,
   not trusted: `q65_rc_probe.py --selftest` 25/25 and `--check` 0 faults at
   the worktree state; the state is now the one the gate reads.

2. cycle 7 is the file that reopened the census. forge's 6e8f577 adds
   `wizard-work/forge/verify_probe_forge_refactor.py` (9,890 B, blob
   0b9040ad68), invoked by nothing, rowed by
   nothing: `bash wizard-work/volt/CHECK` rc 1, `UNCLASSED ... 1 unclassed of
   110`, and `q63_verify_scan.py` `NEW_UNWIRED 1` -- both legs, two classes,
   one file. Graded by execution through the runner (PASS rc 0, 0.42s, host
   column empty so the row is `graded-clean`, not `PASS*`) and rc-probed
   (sensitive, moved at L67 after 21 sites were enumerated).

Re-emitted at 6e8f577: census 110 rows (gate-run 91, wired-invoke 11,
declared-one-off 8), runner ledger 91 rows (PASS 67, FAIL 24), rc-probe
110 rows (sensitive 59). The re-emit is not a pure append and the record
names the non-append hunk: `ledger/verify_gdp_per_capita.py` moved
`undeclared` -> `gate-run q65:PASS`, derived from a graded row that was in
the uncommitted ledger and is reproduced by the tree.
2026-10-25 17:01:51 +00:00

805 lines
36 KiB
Python

#!/usr/bin/env python3
"""q65_rc_probe.py -- ask a committed verifier, by execution, whether its exit
code can move at all (Q65, volt lane, 2026-10-25, cycle 6).
WHY THIS EXISTS
Cycle 5 answered "does a verifier have a failure path" statically: an AST scan
for `raise` / `assert` / non-zero `exit` / `unittest.main` found 15 of 106
committed verifiers with none, 14 of them carrying a PASS row in the runner
ledger. Static presence is a proxy, and it has two blind spots the tool said
so about itself: an exit path inside a `try/except` that swallows it still
passes the scan, and a present exit path can be unreachable from any verdict.
This is the dynamic question instead, and it does not read the source for
intent. It perturbs the verifier's OWN decisions and watches the process:
flip one comparison operator at a time, re-run the file, and see whether the
exit code changes. If a verifier's rc is unchanged after its comparisons have
been inverted, its rc is not connected to what it measured -- no input could
have made that file say no. That is a stronger statement than "no raise in the
AST", because it is made about a process that actually ran.
The mutation is of the verifier, not of the subject. Conventionally, mutation
testing mutates the SUBJECT and asks whether the test still passes (a killed
mutant means the test discriminates). Here the subject stays exactly as it is
and the ASSERTION is mutated, which inverts the question into the one Q65
needs: does an inverted assertion reach the exit code? A verifier whose rc is
stable under its own inverted assertions has no verdict channel, whatever its
source looks like.
WHAT IT MEASURES -- one row per probed file, one class per row
sensitive baseline rc 0, and >=1 VALID mutant returned a different rc.
The rc is wired to a comparison; the file can fail.
inert baseline rc 0, >=1 valid mutant, every valid mutant returned
the baseline rc, AND every mutable comparison site in the file
was tried. The rc does not respond to any of the file's own
comparisons. This is the finding.
It is a statement about COMPARISONS, not about the world: a
verifier whose rc is `all(exists and is_dir ...)` over
`os.path.isdir` is comparison-inert and can still fail on a
falsified subject (measured: spark/verify_hermes_path.py with
one required path renamed -> rc 1). 3 of the 19 comparison-inert
rows at 7cd4dd5 are of that kind; the other 16 cannot fail at
all, 15 of them static no-verdict printers.
inert-partial the same, but the file has more sites than `--exhaustive-cap`
allowed, so the negative is bounded: nothing moved in the
first N sites, and sites N+1.. were never tried. Explicitly
not the same claim as `inert` -- see WHY THE CLASSES ARE SPLIT.
no-comparison baseline rc 0 and no single-operator comparison to mutate.
Nothing in the file is a decision the rc could carry.
unprobed-baseline the unmutated file already returned a non-zero rc: there is
no pass state to defend, so the question does not apply.
unprobed-timeout the unmutated file did not finish inside its window.
unprobed-mutant every mutant tried was invalid (see below), so nothing was
measured. Not a finding either way.
unprobed-window the verifier's own measurement window (`q65_row_windows.tsv`,
shared with the runner) exceeds `--skip-over`, so it was not
mutated. A 30-minute live-HTTP verifier is not inverted three
times inside a sweep; its rc is the runner's row.
unprobed-error the sweep could not touch the file (the work copy lost it
mid-run). Recorded as a row with its message, so a pruned
scratch directory leaves a readable ledger instead of a
traceback halfway through the population.
A MUTANT IS VALID when its run (a) compiled and (b) finished inside the window
and (c) did NOT print a traceback. Flipping `==` to `!=` can turn a type check
into a TypeError; an rc that moves because the mutant crashed says nothing
about whether a verdict reaches the exit code. Invalid mutants are counted and
discarded, and a file left with none is `unprobed-mutant`, not `inert`.
HOW MUCH IS TRIED -- deterministic spread, so the figure is reproducible
`--max-mutants` (default 3) sites are taken from the sorted list of mutable
comparison line numbers by even spacing, so a 76-site file is probed near its
beginning, middle and end rather than at its first three lines. No randomness:
the same rev and the same `--max-mutants` select the same sites.
If those first sites all return the baseline rc, the sample is NOT the
verdict: every remaining site is tried too, up to `--exhaustive-cap`
(default 40) per file; 0 means no cap. Only a file whose whole site list was
tried can be called `inert`; a file stopped by the cap is `inert-partial`.
WHY THE CLASSES ARE SPLIT -- a measured false positive, not a hypothetical
Cycle 6's first sweep wrote `inert` for `forge/verify_chunker_refactor.py`
from a 3-of-11 sample: sites 58, 105, 150 (one of which crashed). Swept
exhaustively in a scratch copy, that file's rc moves at 3 of its 11 sites
(L123 -> 1, L139 -> 1, L142 -> 2) and the sample happened to hit none of
them. The file can fail; the sample said it could not. `inert` is therefore
a statement about every comparison in the file or it is not made.
WHERE IT WRITES
`--emit` mutates a COPY, never this worktree. The copy is a detached `git
worktree` at `--rev` -- a real checkout, not an export, because a verifier
that reads git plumbing returns a non-zero rc in a bare export and that
non-zero would be the copy's defect graded as the verifier's baseline. The
tool refuses (rc 2) a `--work` equal to the repository root -- a gate that
edits the tree it grades is the defect reply 93 §1 already measured once.
Every mutant is written, run, and restored inside a `try/finally`; the file
hash is re-checked after each file and a mismatch is rc 1, not a silent pass.
`--emit` writes the TSV (`--tsv`) and appends progress as it goes, so a sweep
that is interrupted leaves a readable partial ledger rather than nothing.
`--check` is the fast half and what the lane's CHECK runs: it re-reads the TSV,
binds every row to the blob at `--rev` (a row graded a file that has since
changed is STALE), re-derives the class counts from the rows, and faults on a
row that contradicts its own class (a `sensitive` row with no moving line, an
`inert` row with no valid mutant, a `no-comparison` row that lists sites).
It does NOT require population coverage: that is the census's job
(`q63_verify_scan.py`) and the runner's, and duplicating it here would make
two gates red for one added file. It prints the coverage gap instead.
rc 0 the ledger is internally consistent and content-bound
rc 1 a fault it graded: STALE, MISSING, an unknown class, or a row that
contradicts its class
rc 2 it could not grade at all (no TSV, not a git repo, --work is the tree)
"""
import argparse
import ast
import hashlib
import json
import os
import subprocess
import sys
import tempfile
import time
HERE = os.path.dirname(os.path.abspath(__file__))
DEFAULT_TSV = os.path.join(HERE, "q65_rc_probe.tsv")
DEFAULT_REPO = os.path.abspath(os.path.join(HERE, "..", ".."))
SCRATCH_ROOT = "/root/.hermes/profiles/volt/cache/scratch"
CLASSES = ("sensitive", "inert", "inert-partial", "no-comparison",
"unprobed-baseline", "unprobed-timeout", "unprobed-mutant",
"unprobed-window", "unprobed-error")
FLIP = {
ast.Eq: ast.NotEq, ast.NotEq: ast.Eq,
ast.Lt: ast.Gt, ast.Gt: ast.Lt,
ast.LtE: ast.GtE, ast.GtE: ast.LtE,
ast.In: ast.NotIn, ast.NotIn: ast.In,
ast.Is: ast.IsNot, ast.IsNot: ast.Is,
}
TRACEBACK = "Traceback (most recent call last)"
# ---------------------------------------------------------------- source side
def mutable_lines(src):
"""Line numbers of single-operator comparisons that FLIP can invert."""
try:
tree = ast.parse(src)
except SyntaxError:
return []
lines = set()
for node in ast.walk(tree):
if (isinstance(node, ast.Compare) and len(node.ops) == 1
and type(node.ops[0]) in FLIP):
lines.add(node.lineno)
return sorted(lines)
def spread(picks, n):
"""Even-spaced deterministic subset of a sorted list, n or fewer."""
if n <= 0 or not picks:
return []
if len(picks) <= n:
return list(picks)
if n == 1:
return [picks[0]]
step = (len(picks) - 1) / float(n - 1)
out = []
for i in range(n):
idx = int(round(i * step))
if picks[idx] not in out:
out.append(picks[idx])
return out
def mutate(src, line):
"""Return source with the comparison at `line` inverted, or None."""
tree = ast.parse(src)
hits = [n for n in ast.walk(tree)
if isinstance(n, ast.Compare) and n.lineno == line
and len(n.ops) == 1 and type(n.ops[0]) in FLIP]
if not hits:
return None
hits[0].ops[0] = FLIP[type(hits[0].ops[0])]()
return ast.unparse(ast.fix_missing_locations(tree)) + "\n"
# ---------------------------------------------------------------- process side
def run_file(path, timeout):
"""Run one verifier the way the ledger does: cwd is the file's directory."""
t0 = time.time()
try:
p = subprocess.run([sys.executable, os.path.basename(path)],
cwd=os.path.dirname(path) or ".",
capture_output=True, text=True, timeout=timeout)
return {"rc": p.returncode, "traceback": TRACEBACK in p.stderr,
"seconds": round(time.time() - t0, 2)}
except subprocess.TimeoutExpired:
return {"rc": "TIMEOUT", "traceback": False,
"seconds": round(time.time() - t0, 2)}
def read_windows(path):
"""path -> seconds, from the runner's per-row window table (may be absent)."""
table = {}
if not path or not os.path.isfile(path):
return table
with open(path, encoding="utf-8") as fh:
for line in fh:
line = line.rstrip("\n")
if not line or line.startswith("#"):
continue
cells = line.split("\t")
if len(cells) >= 2:
try:
table[cells[0]] = float(cells[1])
except ValueError:
continue
return table
def probe(path, max_mutants, timeout, log=None, exhaustive_cap=40):
"""Baseline, up to max_mutants sample sites, then every remaining site.
The sample is the cheap question; the exhaustive pass is what licenses the
word `inert`. A file stopped by `exhaustive_cap` is inert-partial.
"""
with open(path, encoding="utf-8") as fh:
src = fh.read()
before = hashlib.sha256(src.encode()).hexdigest()
sites = mutable_lines(src)
base = run_file(path, timeout)
row = {"path": path, "baseline_rc": base["rc"], "sites": len(sites),
"tried": 0, "valid": 0, "moved_at": "-", "seconds": base["seconds"],
"cls": None, "note": ""}
if base["rc"] == "TIMEOUT":
row["cls"] = "unprobed-timeout"
return row
if base["rc"] != 0:
row["cls"] = "unprobed-baseline"
row["note"] = "baseline rc %s" % base["rc"]
return row
if not sites:
row["cls"] = "no-comparison"
return row
tries = spread(sites, max_mutants)
if len(tries) < len(sites):
# The negative half of the class needs every site, not the sample.
rest = [s for s in sites if s not in tries]
cap = len(rest) if exhaustive_cap == 0 else max(0, exhaustive_cap - len(tries))
tries += spread(rest, cap)
for line in tries:
mutant = mutate(src, line)
if mutant is None:
continue
row["tried"] += 1
with open(path, "w", encoding="utf-8") as fh:
fh.write(mutant)
try:
got = run_file(path, timeout)
finally:
with open(path, "w", encoding="utf-8") as fh:
fh.write(src)
row["seconds"] += got["seconds"]
valid = got["rc"] != "TIMEOUT" and not got["traceback"]
if not valid:
if log:
log(" L%d invalid mutant (%s)" % (line, got["rc"]))
continue
row["valid"] += 1
if log:
log(" L%d valid mutant rc=%s" % (line, got["rc"]))
if got["rc"] != base["rc"]:
row["cls"] = "sensitive"
row["moved_at"] = line
break
if row["cls"] != "sensitive":
if not row["valid"]:
row["cls"] = "unprobed-mutant"
elif row["tried"] >= len(sites):
row["cls"] = "inert"
else:
row["cls"] = "inert-partial"
row["note"] = "%d of %d sites tried (cap %d)" % (
row["tried"], len(sites), exhaustive_cap)
after = hashlib.sha256(open(path, encoding="utf-8").read().encode()).hexdigest()
if after != before:
raise RuntimeError("restore failed for %s" % path)
return row
# ---------------------------------------------------------------- git plumbing
def tree_blobs(repo, rev):
out = subprocess.run(["git", "-C", repo, "ls-tree", "-r", rev],
capture_output=True, text=True)
if out.returncode != 0:
raise SystemExit("q65_rc_probe: cannot read tree at %s: %s"
% (rev, out.stderr.strip()))
blobs = {}
for line in out.stdout.splitlines():
meta, path = line.split("\t", 1)
parts = meta.split()
if len(parts) >= 3 and parts[1] == "blob":
blobs[path] = parts[2]
return blobs
def population(blobs):
return sorted(p for p in blobs
if os.path.basename(p).startswith("verify_")
and p.endswith(".py")
and p.startswith("wizard-work/"))
def strip_basename(path):
return path
# ---------------------------------------------------------------------- modes
def read_tsv(path):
rows, head = [], {}
with open(path, encoding="utf-8") as fh:
for line in fh:
line = line.rstrip("\n")
if not line.strip():
continue
if line.startswith("#"):
for key in ("rev", "rows", "committed"):
if line.startswith("# %s " % key):
head[key] = line.split(None, 2)[2].split()[0]
continue
cells = line.split("\t")
if len(cells) != 9:
raise ValueError("row has %d cells, want 9: %r" % (len(cells), line))
rows.append(dict(zip(
("cls", "path", "baseline_rc", "sites", "tried", "valid",
"moved_at", "blob", "seconds"), cells)))
return head, rows
def write_header(fh, rev, rows, committed):
fh.write("# Q65 rc-sensitivity ledger -- one row per PROBED committed verifier.\n")
fh.write("# Written by q65_rc_probe.py --emit. A row states a measurement, not an\n")
fh.write("# exemption: --check faults on a row that contradicts its class or whose\n")
fh.write("# file has changed since (STALE).\n")
fh.write("# columns: class, path, baseline_rc, sites, tried, valid, moved_at, blob12, seconds\n")
fh.write("# rev %s\n" % rev)
fh.write("# rows %d\n" % rows)
fh.write("# committed %d\n" % committed)
def do_emit(args):
repo = os.path.abspath(args.repo)
rev = subprocess.run(["git", "-C", repo, "rev-parse", args.rev],
capture_output=True, text=True)
if rev.returncode != 0:
print("q65_rc_probe: not a git tree at %s" % repo, file=sys.stderr)
return 2
rev = rev.stdout.strip()
blobs = tree_blobs(repo, rev)
pop = population(blobs)
windows = read_windows(args.windows)
prev_rows = []
if args.merge and os.path.isfile(args.tsv):
try:
_, prev_rows = read_tsv(args.tsv)
except ValueError:
prev_rows = []
print("q65_rc_probe: merging into %d existing row(s) from %s"
% (len(prev_rows), args.tsv))
if args.only:
pop = [p for p in pop if args.only in p]
if args.limit:
pop = pop[:args.limit]
if args.work:
work = os.path.abspath(args.work)
if work == os.path.realpath(repo) or work == repo:
print("q65_rc_probe: --work is the repository itself; refusing to "
"mutate the tree this gate grades", file=sys.stderr)
return 2
else:
work = os.path.join(SCRATCH_ROOT, "rcprobe-%d" % int(time.time()))
if not os.path.isdir(work):
os.makedirs(work)
if not os.path.isdir(os.path.join(work, ".git")) and not os.path.exists(
os.path.join(work, ".git")):
# A real git worktree, not an export: a verifier that reads git plumbing
# (rev-parse, ls-tree, log) returns a non-zero rc in a bare export, and a
# non-zero BASELINE is a row with no pass state to defend -- the sweep
# would grade its own copy's defect instead of the verifier.
add = subprocess.run(["git", "-C", repo, "worktree", "add", "--detach",
work, rev], capture_output=True, text=True)
if add.returncode != 0:
print("q65_rc_probe: cannot add a worktree at %s: %s"
% (rev[:12], add.stderr.strip()), file=sys.stderr)
return 2
print("q65_rc_probe -- rev %s, %d population file(s), work copy %s"
% (rev[:12], len(pop), work))
rows = []
for i, rel in enumerate(pop, 1):
path = os.path.join(work, rel)
if not os.path.isfile(path):
rows.append({"cls": "unprobed-mutant", "path": rel, "baseline_rc": "-",
"sites": "-", "tried": 0, "valid": 0, "moved_at": "-",
"blob": blobs.get(rel, "-")[:12], "seconds": 0.0,
"note": "absent from the work copy"})
continue
print("[%d/%d] %s" % (i, len(pop), rel))
window = windows.get(rel, args.timeout)
if window > args.skip_over:
row = {"cls": "unprobed-window", "path": rel, "baseline_rc": "-",
"sites": "-", "tried": 0, "valid": 0, "moved_at": "-",
"seconds": 0.0, "note": "%ss window" % int(window)}
row["blob"] = blobs.get(rel, "-")[:12]
rows.append(row)
print(" -> %-17s window %ss exceeds --skip-over %ss"
% (row["cls"], int(window), int(args.skip_over)))
continue
try:
row = probe(path, args.max_mutants, window, log=lambda m: print(m),
exhaustive_cap=getattr(args, "exhaustive_cap", 40))
except (OSError, RuntimeError) as exc:
# A work copy that lost a file mid-sweep (a scratch pruner, a full
# disk) must be a row, not a traceback: the sweep's ledger stays
# readable and the row says what happened.
row = {"cls": "unprobed-error", "path": rel, "baseline_rc": "-",
"sites": "-", "tried": 0, "valid": 0, "moved_at": "-",
"seconds": 0.0, "note": str(exc)[:120]}
row["blob"] = blobs.get(rel, "-")[:12]
rows.append(row)
print(" -> %-17s %s" % (row["cls"], str(exc)[:90]))
continue
row["blob"] = blobs.get(rel, "-")[:12]
row["path"] = rel
rows.append(row)
print(" -> %-17s rc=%s sites=%s tried=%s valid=%s moved_at=%s %ss"
% (row["cls"], row["baseline_rc"], row["sites"], row["tried"],
row["valid"], row["moved_at"], row["seconds"]))
with open(args.tsv, "w", encoding="utf-8") as fh:
write_header(fh, rev, len(rows), len(pop))
for r in rows:
fh.write("\t".join(str(r.get(k, "-")) for k in
("cls", "path", "baseline_rc", "sites", "tried",
"valid", "moved_at", "blob", "seconds")) + "\n")
with open(args.tsv, "w", encoding="utf-8") as fh:
merged = rows
if args.merge and prev_rows:
by_path = {r["path"]: r for r in prev_rows}
for r in rows:
by_path[r["path"]] = r
merged = [by_path[k] for k in sorted(by_path)]
write_header(fh, rev, len(merged), len(population(tree_blobs(repo, rev))))
for r in merged:
fh.write("\t".join(str(r.get(k, "-")) for k in
("cls", "path", "baseline_rc", "sites", "tried",
"valid", "moved_at", "blob", "seconds")) + "\n")
print("wrote %s (%d rows)" % (args.tsv, len(merged)))
print("work copy left at %s" % work)
return 0
def do_check(args):
if not os.path.isfile(args.tsv):
print("q65_rc_probe: no ledger at %s -- cannot grade" % args.tsv,
file=sys.stderr)
return 2
try:
head, rows = read_tsv(args.tsv)
except ValueError as exc:
print("q65_rc_probe: %s" % exc, file=sys.stderr)
return 1
repo = os.path.abspath(args.repo)
try:
blobs = tree_blobs(repo, args.rev)
except SystemExit as exc:
print(str(exc), file=sys.stderr)
return 2
faults = []
counts = {c: 0 for c in CLASSES}
for r in rows:
if r["cls"] not in CLASSES:
faults.append("UNKNOWN_CLASS %s: %r" % (r["path"], r["cls"]))
continue
counts[r["cls"]] += 1
if r["path"] not in blobs:
faults.append("MISSING %s: row in the ledger, absent at %s" % (r["path"], args.rev[:12]))
continue
if not blobs[r["path"]].startswith(r["blob"]):
faults.append("STALE %s: row graded blob %s, tree holds %s"
% (r["path"], r["blob"], blobs[r["path"]][:12]))
try:
moved = int(r["moved_at"]) if r["moved_at"] not in ("-", "") else None
tried, valid = int(r["tried"]), int(r["valid"])
sites = int(r["sites"]) if r["sites"] not in ("-", "") else None
except ValueError:
faults.append("BAD_CELL %s: non-numeric tried/valid/moved_at" % r["path"])
continue
if r["cls"] == "sensitive" and not moved:
faults.append("CONTRADICTION %s: sensitive with no moving line" % r["path"])
if r["cls"] == "inert" and not (valid >= 1 and sites is not None
and tried >= sites and moved is None):
faults.append("CONTRADICTION %s: inert with valid=%d tried=%s sites=%s moved=%s"
% (r["path"], valid, tried, sites, moved))
if r["cls"] == "inert-partial" and not (valid >= 1 and sites is not None
and tried < sites and moved is None):
faults.append("CONTRADICTION %s: inert-partial with valid=%d tried=%s "
"sites=%s moved=%s"
% (r["path"], valid, tried, sites, moved))
if r["cls"] == "no-comparison" and sites:
faults.append("CONTRADICTION %s: no-comparison but lists %d site(s)"
% (r["path"], sites))
if r["cls"] == "unprobed-mutant" and valid:
faults.append("CONTRADICTION %s: unprobed-mutant with %d valid mutant(s)"
% (r["path"], valid))
committed = population(blobs)
probed = {r["path"] for r in rows}
print("q65_rc_probe -- rev %s, %d row(s), %d committed verifier(s)"
% (args.rev[:12], len(rows), len(committed)))
for c in CLASSES:
print(" %-18s %d" % (c, counts[c]))
print(" rc-sensitive %d of %d probed of %d committed"
% (counts["sensitive"], counts["sensitive"] + counts["inert"]
+ counts["inert-partial"] + counts["no-comparison"], len(committed)))
gap = [p for p in committed if p not in probed]
if gap:
print(" not probed %d (population coverage is the census's leg, "
"not this one)" % len(gap))
if faults:
for f in faults:
print(" FAULT %s" % f)
print("q65_rc_probe: %d fault(s)" % len(faults))
return 1
print("q65_rc_probe: 0 fault(s) -- every row content-bound and consistent "
"with its class")
return 0
# -------------------------------------------------------------------- selftest
def selftest():
tmp = tempfile.mkdtemp(prefix="rcprobe-selftest-", dir=SCRATCH_ROOT)
cells = []
def cell(name, ok, detail=""):
cells.append((name, bool(ok), detail))
def mk(name, body):
d = os.path.join(tmp, name)
os.makedirs(d, exist_ok=True)
p = os.path.join(d, "verify_x.py")
with open(p, "w", encoding="utf-8") as fh:
fh.write(body)
return p
# c1 sensitive: the inverted assertion reaches sys.exit
p = mk("sensitive", "import sys\nx = 2\nif x != 2:\n sys.exit(0)\n"
"if x == 2:\n print('ok')\nelse:\n sys.exit(1)\n")
r = probe(p, 3, 20)
cell("c1 an inverted assertion that reaches sys.exit is sensitive",
r["cls"] == "sensitive" and r["moved_at"], str(r))
# c2 inert: comparisons whose result is only printed
p = mk("inert", "x = 2\ny = 3\nprint('x==2', x == 2)\nprint('y<x', y < x)\n")
r = probe(p, 3, 20)
cell("c2 comparisons that only print leave the rc unmoved (inert)",
r["cls"] == "inert" and r["valid"] >= 1, str(r))
# c3 no comparison at all
p = mk("nocomp", "print('all checks passed')\n")
r = probe(p, 3, 20)
cell("c3 a file with no comparison is no-comparison",
r["cls"] == "no-comparison", str(r))
# c4 baseline non-zero
p = mk("baseline", "import sys\nsys.exit(2)\n")
r = probe(p, 3, 20)
cell("c4 a non-zero baseline is unprobed-baseline, not a pass",
r["cls"] == "unprobed-baseline", str(r))
# c5 every mutant invalid (the flip reaches an unreachable branch and crashes)
p = mk("invalid", "x = None\nif x == None:\n print('none')\nelse:\n"
" print(x.attr)\n")
r = probe(p, 3, 20)
cell("c5 a mutant that crashes is invalid, and no valid mutant is not inert",
r["cls"] == "unprobed-mutant" and r["tried"] == 1 and r["valid"] == 0, str(r))
cell("c5b the crashed-mutant file was restored byte for byte",
open(p).read() == "x = None\nif x == None:\n print('none')\nelse:\n"
" print(x.attr)\n")
# c5c one invalid mutant beside a valid one: counted, discarded, still graded
p = mk("mixed", "x = None\ny = 1\nif x == None:\n print('none')\nelse:\n"
" print(x.attr)\nif y == 1:\n print('y')\n")
r = probe(p, 3, 20)
cell("c5c an invalid mutant is counted and discarded, not counted as a test",
r["cls"] == "inert" and r["tried"] == 2 and r["valid"] == 1, str(r))
# c5d the sample is not the verdict: a moving site outside the 3-site
# even-spaced sample is found by the exhaustive pass
body5d = ("import sys\n"
"x1 = 1\n"
"x2 = 2\n"
"x3 = 3\n"
"x4 = 4\n"
"x5 = 5\n"
"print(x1 == 1)\n"
"if x2 == 2:\n"
" sys.exit(0)\n"
"print(x3 == 3)\n"
"print(x4 == 4)\n"
"print(x5 == 5)\n"
"sys.exit(1)\n")
p = mk("outside_sample", body5d)
r = probe(p, 3, 20)
cell("c5d a moving site the 3-site sample misses is still found",
r["cls"] == "sensitive" and r["moved_at"] == 8 and r["sites"] == 5,
str(r))
cell("c5d2 the sample really did miss it (site 8 is not in spread(sites,3))",
8 not in spread(mutable_lines(body5d), 3),
str(spread(mutable_lines(body5d), 3)))
# c5e the same file under a cap that stops before the moving site is
# inert-partial, not inert and not sensitive
p = mk("capped", body5d)
r = probe(p, 3, 20, exhaustive_cap=3)
cell("c5e a cap that stops before the moving site is inert-partial",
r["cls"] == "inert-partial" and r["tried"] == 3 and r["sites"] == 5,
str(r))
# c6 restore on a sensitive file
p = mk("restore", "import sys\nx = 1\nif x == 1:\n sys.exit(0)\n")
before = open(p, "rb").read()
probe(p, 3, 20)
cell("c6 a probed file is restored byte for byte", open(p, "rb").read() == before)
# c7 spread is deterministic and even
picks = list(range(0, 100))
cell("c7 spread picks evenly and deterministically",
spread(picks, 3) == spread(picks, 3) and len(spread(picks, 3)) == 3
and spread(picks, 3)[0] == 0 and spread(picks, 3)[-1] == 99,
str(spread(picks, 3)))
cell("c7b spread never invents a site", all(s in picks for s in spread(picks, 7)))
# c8 tsv round-trip
tsv = os.path.join(tmp, "led.tsv")
rows = [
{"cls": "sensitive", "path": "a/verify_a.py", "baseline_rc": 0, "sites": 4,
"tried": 2, "valid": 2, "moved_at": 12, "blob": "abc123", "seconds": 0.1},
{"cls": "inert", "path": "a/verify_b.py", "baseline_rc": 0, "sites": 1,
"tried": 1, "valid": 1, "moved_at": "-", "blob": "def456", "seconds": 0.2},
]
with open(tsv, "w") as fh:
write_header(fh, "f" * 40, len(rows), 2)
for r in rows:
fh.write("\t".join(str(r[k]) for k in ("cls", "path", "baseline_rc",
"sites", "tried", "valid",
"moved_at", "blob", "seconds")) + "\n")
head, back = read_tsv(tsv)
cell("c8 the ledger round-trips its rows and its header",
len(back) == 2 and head.get("rows") == "2" and back[0]["moved_at"] == "12",
str(head))
# c9 a short row is refused, not silently skipped
bad = os.path.join(tmp, "short.tsv")
open(bad, "w").write("# rev " + "f" * 40 + "\n# rows 1\n# committed 1\n"
"inert\ta/verify_a.py\t0\t1\n")
try:
read_tsv(bad)
cell("c9 a short row is refused", False)
except ValueError:
cell("c9 a short row is refused", True)
# c10 --check refuses a --work that is the tree
ns = argparse.Namespace(repo=DEFAULT_REPO, rev="HEAD", tsv=DEFAULT_TSV,
only=None, limit=0, work=os.path.realpath(DEFAULT_REPO),
max_mutants=1, timeout=1,
windows=os.path.join(HERE, "q65_row_windows.tsv"),
skip_over=300.0, merge=False)
cell("c10 --emit refuses --work equal to the repo root", do_emit(ns) == 2)
# c12 the window table parses, and a malformed row is skipped not fatal
wt = os.path.join(tmp, "win.tsv")
open(wt, "w").write("# path\ts\twhy\na/verify_a.py\t1800\tlive HTTP\n"
"b/verify_b.py\tnotanumber\tbroken\n")
w = read_windows(wt)
cell("c12 the window table parses a path and skips a malformed row",
w.get("a/verify_a.py") == 1800.0 and "b/verify_b.py" not in w, str(w))
# c13 a verifier over the skip-over is unprobed-window, and the emit still succeeds
one = os.path.join(tmp, "one.tsv")
ns3 = argparse.Namespace(repo=DEFAULT_REPO, rev="HEAD", tsv=one,
only="wizard-work/sphinx/archive-index-2026-10-23/verify_index.py",
limit=0, work=os.path.join(tmp, "work"),
max_mutants=1, timeout=1,
windows=os.path.join(HERE, "q65_row_windows.tsv"),
skip_over=1.0, merge=False)
rc = do_emit(ns3)
head2, rows2 = read_tsv(one)
cell("c13 a file whose window exceeds --skip-over is unprobed-window, rc 0",
rc == 0 and len(rows2) == 1 and rows2[0]["cls"] == "unprobed-window",
str(rows2))
# c15 --merge keeps a row this run did not probe
ns4 = argparse.Namespace(repo=DEFAULT_REPO, rev="HEAD", tsv=one,
only="wizard-work/spark/verify_sum.py",
limit=0, work=os.path.join(tmp, "work"),
max_mutants=1, timeout=30,
windows=os.path.join(HERE, "q65_row_windows.tsv"),
skip_over=300.0, merge=True)
rc = do_emit(ns4)
head3, rows3 = read_tsv(one)
kept = [r for r in rows3 if r["path"].endswith("archive-index-2026-10-23/verify_index.py")]
fresh = [r for r in rows3 if r["path"].endswith("spark/verify_sum.py")]
cell("c15 --merge keeps the unprobed row beside the new one",
rc == 0 and len(rows3) == 2 and kept and fresh, str(rows3))
# c14 a file the sweep cannot read raises (do_emit turns it into a row)
try:
probe(os.path.join(tmp, "absent", "verify_x.py"), 1, 5)
cell("c14 an unreadable file raises OSError, caught into a row", False)
except OSError:
cell("c14 an unreadable file raises OSError, caught into a row", True)
# c16 --check enforces the coverage half of the inert claim
blobs_now = tree_blobs(DEFAULT_REPO, "HEAD")
real = sorted(p for p in blobs_now
if p.startswith("wizard-work/")
and os.path.basename(p).startswith("verify_"))[0]
b12 = blobs_now[real][:12]
def _led(cls, tried, sites):
f = os.path.join(tmp, "chk-%s-%d-%d.tsv" % (cls, tried, sites))
with open(f, "w", encoding="utf-8") as fh:
write_header(fh, "HEAD", 1, 1)
fh.write("\t".join([cls, real, "0", str(sites), str(tried),
"1", "-", b12, "0.1"]) + "\n")
return do_check(argparse.Namespace(repo=DEFAULT_REPO, rev="HEAD", tsv=f))
cell("c16 inert with sites untried is a contradiction (rc 1)",
_led("inert", 2, 5) == 1)
cell("c16b inert with every site tried is consistent (rc 0)",
_led("inert", 5, 5) == 0)
cell("c16c inert-partial with sites untried is consistent (rc 0)",
_led("inert-partial", 2, 5) == 0)
cell("c16d inert-partial with every site tried is a contradiction (rc 1)",
_led("inert-partial", 5, 5) == 1)
# c11 --check on a missing ledger is rc 2 (cannot grade), not rc 0
ns2 = argparse.Namespace(repo=DEFAULT_REPO, rev="HEAD",
tsv=os.path.join(tmp, "nope.tsv"))
cell("c11 a missing ledger is rc 2, not a pass", do_check(ns2) == 2)
ok = sum(1 for _, o, _ in cells if o)
for name, o, detail in cells:
print("%s %s%s" % ("ok " if o else "FAIL", name,
"" if o else " [%s]" % detail))
print("selftest: %d cells, %d passed, %d failed" % (len(cells), ok, len(cells) - ok))
return 0 if ok == len(cells) else 1
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--repo", default=DEFAULT_REPO)
ap.add_argument("--rev", default="HEAD")
ap.add_argument("--tsv", default=DEFAULT_TSV)
ap.add_argument("--work", help="copy dir to mutate (default: fresh scratch dir)")
ap.add_argument("--max-mutants", type=int, default=3)
ap.add_argument("--exhaustive-cap", type=int, default=40,
help="sites per file tried before an inert verdict is "
"downgraded to inert-partial; 0 = no cap")
ap.add_argument("--timeout", type=float, default=90.0,
help="window for a file the window table does not name")
ap.add_argument("--windows", default=os.path.join(HERE, "q65_row_windows.tsv"),
help="per-path measurement windows, shared with the runner")
ap.add_argument("--skip-over", type=float, default=300.0,
help="a verifier whose window exceeds this is not mutated; "
"grading it is the runner's row, not this sweep's")
ap.add_argument("--only", help="with --emit: only paths containing this substring")
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--emit", action="store_true")
ap.add_argument("--merge", action="store_true",
help="with --emit: keep rows for paths this run did not probe")
ap.add_argument("--check", action="store_true")
ap.add_argument("--selftest", action="store_true")
args = ap.parse_args()
if args.selftest:
return selftest()
if args.check:
return do_check(args)
if args.emit:
return do_emit(args)
ap.print_help()
return 2
if __name__ == "__main__":
sys.exit(main())