Files

635 lines
25 KiB
Python

#!/usr/bin/env python3
"""Closed-artifact regression guard: a repaired blob stays repaired.
Q127. `899ce4d` ("spark: add verified Fibonacci calculator with assertions", a
sweep of ten files) rewrote two files that two closed items had repaired the day
before, and put the pre-repair bytes back:
wizard-work/spark/snippet.py Q109 -> reverted to the 3857b60 blob
wizard-work/spark/python-env-check.py Q120 -> reverted to the c166bf1 blob
Neither item's record was edited and neither cell was deleted, so both gates
went red at HEAD and stayed red: `bash wizard-work/spark/CHECK-env-verifiers`
read `Ran 16 tests ... FAILED (failures=9)` rc 1, and
`python3 wizard-work/spark/test_python_env_check.py` read
`Ran 7 tests ... FAILED (failures=5, errors=2)` rc 1. A red gate is only a guard
if something runs it, and the spark lane's own CHECK runs none of its cells
(it grades absolute paths under /root instead -- the Q109/Q120 class).
This file is the missing run: it pins the bytes each repair landed, so a later
whole-file overwrite is a named fault rather than a silent regression. The pins
are data, not a heuristic, and the pre-repair digests are data too, so the
diagnosis can say "reverted" instead of "changed".
Legs (each prints PASS/FAIL/INFO and names its subject):
pin:<path> sha256 equals the blob the repair commit landed
cell:<name> the item's own cell still runs green
run:<path> a rebound Q126 test file runs rc 0 from its own directory
contract:<name> the repaired artifact's documented behaviour, by subprocess
env:<name> INFO only -- host packages the Q120 cell is coupled to
selftest eight cells: one control, six one-edit mutants, one
classifier cell
2026-10-30, second sweep: `afb231d` ("spark: add disk usage analyzer script and
report") put all three of the pinned artifact blobs back AND reverted the seven
spark test files Q126 had rebound, byte for byte, in one commit that advertises
one new script. Nothing ran this guard -- `1cb001a` wired it into
`wizard-work/spark/CHECK` and that commit put the six-line wiring back too -- so
the reverts stood until the repo gate's `import-resolve` leg named the test
files. The Q126 files are pinned and run here now, and `tools/verify.py` runs
this guard as the `closed-artifacts` leg, so the next sweep reds the primary
gate instead of waiting for a reader to notice.
Exit codes follow the repo's tools/ contract:
0 = every pinned blob and every cell is as the closed records left it
1 = a graded fault (drift, revert, absent artifact, broken cell/contract)
2 = could not grade (bad root, no python3, selftest fixture failure)
Nothing here imports the graded artifacts: every reading is a digest, a
subprocess, or a file read, so the guard cannot agree with the code by sharing
it. Paths resolve from __file__ (or --root), so a clone grades its own tree.
"""
import argparse
import hashlib
import json
import os
import shutil
import subprocess
import sys
import tempfile
SPARK = os.path.join("wizard-work", "spark")
# The digest each repair landed, and the digest it replaced. `repaired` is the
# pin; `pre_repair` exists only so the diagnosis can name a revert.
PINS = {
"snippet.py": {
"repaired": "3eb85613fce77d5f0b9e31ca7d9e1ff6cd83dd9fd9c63e8268ed69f576a8c3b2",
"pre_repair": "dc4dbeeee3a2dc8d6c4babdf72e4aaf89dcadfa4cbd24146ef5b96aabb239e2a",
"item": "Q109",
"landed": "ebba640",
"pre_rev": "3857b60",
},
"python-env-check.py": {
"repaired": "747dcc3afdccceb2c9fca2eb3cb2e933edac3021c40317f979f630d05c53fc56",
"pre_repair": "51e7a52334a2a487320f68bc20ae8f7311f94ba47de6ee375acc05a349bba037",
"item": "Q120",
"landed": "bda365c",
"pre_rev": "c166bf1",
},
"verify_hermes_setup.sh": {
"repaired": "b58861a11d38fa01e7f9731e39e435db82b80a63e3007548bed94c8f352dbc6f",
"pre_repair": "0af7884e8192d10e6c9cf5761e094fb31e7b62fd02433f2cf2201b57bd26031e",
"item": "Q109",
"landed": "ebba640",
"pre_rev": "3857b60",
},
# Q126 rebound nine host-bound test files to the module beside them. The
# next spark sweep (`afb231d`, 2026-10-30T20:17:47Z) put all seven of the
# spark ones back to the pre-repair bytes, byte for byte. They are pinned
# here for the same reason as the artifacts above: a repair that can be
# overwritten without a name is not a repair, it is a coincidence.
"test_array_intersection.py": {
"repaired": "d31a5ec9bc9b00a41239f6bac930a46156b234c21e3dfac68e9888a8f252cf18",
"pre_repair": "6f29649d52b50b166ce16fafc3a6021d1068301c8093d3a95cbc939afd9ca673",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_counter_util.py": {
"repaired": "8648a28152a557b2a7b2466d8232955f5f3fdfcc7445894285fd1c52882898a8",
"pre_repair": "d837f183440640416cdd8c3020ee6035288d099a704e441d2726810408645411",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_email_validator.py": {
"repaired": "b698838fc4055c1bc3a30f054855b2cf19b79023b27636b53a7dffc5b9278c37",
"pre_repair": "95d8d77c1ee8e75f1438d3915ffc2e8f07d397a8b3d67f38d5e25e81cb84540f",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_hermes.py": {
"repaired": "b8dad112c1f3e170e3b82e8cfb074e1f30e2648f9c62b38e30aaf837dc83feb0",
"pre_repair": "87f552a14a9945ce9cfdaf29bc959a394fcc779eda519b824ece80d27e99ab91",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_prime_factorizer.py": {
"repaired": "3d409c79cbeb4d068d73f0cb8dd7e539f837e5afdc9c8770584d1e7db263a9e1",
"pre_repair": "4be81385dfd01a00363977ef6ff787741d1a0ecc9892f030aaefd3b0386509e1",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_url_title_extractor.py": {
"repaired": "411d781a8d380497def3d4811900f313247d951aee7cef965675dccbdc9b311a",
"pre_repair": "b6ea8fc299a7caa24cacd2ad7aaaed302533ecc7b82f0866265612f92c1696ca",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
"test_validator.py": {
"repaired": "0dd63e37b805090b9e290f5a1707daae45e4f6275a4ffbca1d9c3416f6a84165",
"pre_repair": "665e21645a6cd0d49c55b269e546617917741ac3f592473251c29f59515edc03",
"item": "Q126",
"landed": "b14d73a",
"pre_rev": "afb231d",
},
}
# A pin proves the bytes; this proves those bytes still measure something. The
# seven Q126 files read the module beside them AND data files that are not
# pinned one by one (test_schema.json and the two data files), so a deleted
# fixture would leave every pin green and the suite dead.
RUNS = [
"test_array_intersection.py",
"test_counter_util.py",
"test_email_validator.py",
"test_hermes.py",
"test_prime_factorizer.py",
"test_url_title_extractor.py",
"test_validator.py",
]
# The cell that grades each artifact must still be here for the cell leg to run.
CELL_FILES = ["test_env_verifiers.py", "test_python_env_check.py"]
# Host packages the Q120 cell asserts present. Absent here on 2026-10-30, so the
# cell reads red for the environment and not for the code -- reported, never
# graded, because a tree pin must not depend on what this host has installed.
ENV_PACKAGES = ["numpy", "yaml"]
DECLARED = {"yaml", "numpy", "PIL", "jsonschema"}
def sha256(path):
h = hashlib.sha256()
with open(path, "rb") as fh:
for chunk in iter(lambda: fh.read(1 << 16), b""):
h.update(chunk)
return h.hexdigest()
def classify(rel, digest):
"""repaired / reverted / drifted -- the naming half of the pin leg."""
pin = PINS[rel]
if digest == pin["repaired"]:
return "repaired"
if digest == pin["pre_repair"]:
return "reverted"
return "drifted"
def run(cmd, cwd=None, timeout=180):
return subprocess.run(
cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout
)
class Report:
def __init__(self):
self.passed = []
self.failed = []
self.info = []
self.unreadable = []
def ok(self, leg, detail):
self.passed.append(leg)
print("PASS %s: %s" % (leg, detail))
def bad(self, leg, detail):
self.failed.append(leg)
# `FAIL:` -- with the colon -- is the token `tools/verify.py` treats as a
# STRONG hint, so the gate's row carries THIS line rather than the first
# line that happens to contain the word "error" (a passing contract
# line prints "two usage errors rc 2"). Measured on the 2026-10-30
# mutant: the row read FAULT beside a PASS line until the colon was
# added, which is the detail-contradicts-verdict class verify.py cell 13
# exists to catch.
print("FAIL: %s -- %s" % (leg, detail))
def note(self, leg, detail):
self.info.append(leg)
print("INFO %s: %s" % (leg, detail))
def cannot(self, leg, detail):
self.unreadable.append(leg)
print("UNGRADABLE %s: %s" % (leg, detail))
def leg_pins(root, rep):
"""One leg per artifact: the blob is the one its closed record landed."""
for rel in sorted(PINS):
path = os.path.join(root, SPARK, rel)
pin = PINS[rel]
if not os.path.exists(path):
rep.bad("pin:%s" % rel, "absent from %s (Q109/Q120 artifact missing)" % os.path.join(SPARK, rel))
continue
digest = sha256(path)
status = classify(rel, digest)
if status == "repaired":
rep.ok("pin:%s" % rel, "sha256=%s == %s's repair at %s" % (digest[:12], pin["item"], pin["landed"]))
elif status == "reverted":
rep.bad(
"pin:%s" % rel,
"sha256=%s is the %s pre-repair blob -- %s's repair at %s was "
"overwritten; restore with: git show %s:%s/%s"
% (digest[:12], pin["pre_rev"], pin["item"], pin["landed"],
pin["landed"], SPARK, rel),
)
else:
rep.bad(
"pin:%s" % rel,
"sha256=%s is neither the pinned repair (%s) nor the pre-repair "
"blob; re-derive the pin if this edit is intended"
% (digest[:12], pin["repaired"][:12]),
)
def leg_cell_files(root, rep):
for rel in CELL_FILES:
path = os.path.join(root, SPARK, rel)
if os.path.exists(path):
rep.ok("cell-file:%s" % rel, "present, sha256=%s" % sha256(path)[:12])
else:
rep.bad("cell-file:%s" % rel, "the cell that grades these artifacts is gone")
def leg_env_verifiers(root, rep):
"""Q109's own gate, run as the item wrote it."""
gate = os.path.join(root, SPARK, "CHECK-env-verifiers")
if not os.path.exists(gate):
rep.bad("cell:env-verifiers", "CHECK-env-verifiers is absent")
return
proc = run(["bash", gate])
tail = (proc.stdout + proc.stderr).strip().splitlines()
last = tail[-1] if tail else "(no output)"
if proc.returncode == 0:
rep.ok("cell:env-verifiers", "rc 0 -- %s" % last)
elif proc.returncode == 1:
rep.bad("cell:env-verifiers", "rc 1 -- %s" % last)
else:
rep.cannot("cell:env-verifiers", "rc %d -- %s" % (proc.returncode, last))
def leg_pyenv_contract(root, rep):
"""Q120's documented behaviour, measured without importing the script.
These legs hold whatever this host has installed: an absent package is
named, a usage error is rc 2, and the declared set is the lane's four.
"""
script = os.path.join(root, SPARK, "python-env-check.py")
if not os.path.exists(script):
rep.bad("contract:python-env-check", "script is absent")
return
plain = run([sys.executable, script])
if plain.returncode not in (0, 1):
rep.bad("contract:python-env-check", "uninstrumented run exited %d, want 0 or 1: %s"
% (plain.returncode, (plain.stderr or "").strip()[:160]))
return
try:
report = json.loads(plain.stdout)
except ValueError as exc:
rep.bad("contract:python-env-check", "output is not JSON: %s" % exc)
return
packages = report.get("components", {}).get("packages", {})
if set(packages) != DECLARED:
rep.bad("contract:python-env-check",
"declared set %s != %s" % (sorted(packages), sorted(DECLARED)))
return
if report.get("all_ok") != (report.get("missing") == []):
rep.bad("contract:python-env-check",
"all_ok=%r disagrees with missing=%r" % (report.get("all_ok"), report.get("missing")))
return
want_rc = 0 if report["all_ok"] else 1
if plain.returncode != want_rc:
rep.bad("contract:python-env-check",
"exited %d with all_ok=%r, want %d" % (plain.returncode, report["all_ok"], want_rc))
return
for name, result in packages.items():
if "distribution" not in result:
rep.bad("contract:python-env-check", "%s reports no distribution" % name)
return
req = run([sys.executable, script, "--require", "flask"])
try:
req_report = json.loads(req.stdout)
except ValueError:
req_report = {}
if req.returncode != 1 or "flask" not in req_report.get("missing", []):
rep.bad("contract:python-env-check",
"--require flask -> rc %d, missing=%r; want rc 1 and flask named"
% (req.returncode, req_report.get("missing")))
return
for bad_argv in (["--bogus"], ["--require"]):
proc = run([sys.executable, script] + bad_argv)
if proc.returncode != 2:
rep.bad("contract:python-env-check",
"%s -> rc %d, want 2" % (" ".join(bad_argv), proc.returncode))
return
from_root = run([sys.executable, script], cwd=os.path.abspath(os.sep))
try:
root_report = json.loads(from_root.stdout)
except ValueError:
root_report = {}
if root_report.get("missing") != report.get("missing"):
rep.bad("contract:python-env-check",
"reading differs from cwd=/ : %r != %r"
% (root_report.get("missing"), report.get("missing")))
return
rep.ok("contract:python-env-check",
"declared=%d (rc %d), --require flask rc 1, two usage errors rc 2, same from /"
% (len(packages), plain.returncode))
def leg_env(rep):
"""INFO only: what the Q120 cell is coupled to on this host."""
import importlib.util
for name in ENV_PACKAGES:
found = importlib.util.find_spec(name) is not None
rep.note("env:%s" % name,
"installed" if found else
"absent on this host -- test_python_env_check.py reads red for the "
"environment, not for the code")
def leg_runs(root, rep):
"""Q126: the rebound test files run green from their own directory.
Their sys.path is this directory, so rc 0 here means the run graded the
committed module -- and that the data files the file reads are still there.
"""
spark = os.path.join(root, SPARK)
for name in RUNS:
if not os.path.exists(os.path.join(spark, name)):
rep.bad("run:%s" % name, "absent from %s (Q126's cell is gone)" % SPARK)
continue
try:
proc = run([sys.executable, name], cwd=spark, timeout=180)
except subprocess.TimeoutExpired:
rep.bad("run:%s" % name, "timeout after 180s")
continue
if proc.returncode == 0:
rep.ok("run:%s" % name,
"rc 0 from %s -- the run graded the module beside it" % SPARK)
else:
lines = (proc.stdout + proc.stderr).strip().splitlines() or [""]
rep.bad("run:%s" % name, "rc %d -- %s"
% (proc.returncode, lines[-1][:160]))
def guard(root, run_cells=True):
rep = Report()
if not os.path.isdir(os.path.join(root, SPARK)):
print("q127: rc 2 -- %s has no %s directory" % (root, SPARK), file=sys.stderr)
return 2
leg_pins(root, rep)
leg_cell_files(root, rep)
if run_cells:
leg_env_verifiers(root, rep)
leg_runs(root, rep)
leg_pyenv_contract(root, rep)
leg_env(rep)
total = len(rep.passed) + len(rep.failed)
if rep.unreadable:
print("q127: %d leg(s), %d passed, %d fault(s), %d ungradable -- rc 2"
% (total + len(rep.unreadable), len(rep.passed), len(rep.failed), len(rep.unreadable)))
return 2
if rep.failed:
print("q127: %d leg(s), %d passed, %d fault(s) -- rc 1" % (total, len(rep.passed), len(rep.failed)))
return 1
print("q127: %d leg(s), %d passed, 0 fault(s) -- closed artifacts are still repaired"
% (total, len(rep.passed)))
return 0
# ---------------------------------------------------------------- selftest --
# The modules and data files the pinned Q126 cells import. Copied into every
# selftest tree so the control is a tree in which a correct repair reads green:
# without them the cells are right and the fixture is empty, which is a red
# control and looks exactly like a broken repair.
RUN_DEPS = [
"array_intersection.py", "counter_util.py", "email_validator.py",
"json_schema_cli.py", "prime_factorizer.py", "url_title_extractor.py",
"test_schema.json", "valid_test_data.json", "invalid_test_data.json",
]
def build_fixture(dest, repo_root, repairs):
"""Copy the guarded files into `dest`, then apply `repairs` edits."""
spark = os.path.join(dest, SPARK)
os.makedirs(spark)
for rel in list(PINS) + CELL_FILES + ["CHECK-env-verifiers"] + RUN_DEPS:
src = os.path.join(repo_root, SPARK, rel)
if os.path.exists(src):
shutil.copy2(src, os.path.join(spark, rel))
repairs(dest)
return dest
def _revert_snippet(dest):
"""Put the pre-repair bytes back -- exactly what 899ce4d did."""
path = os.path.join(dest, SPARK, "snippet.py")
with open(path, "w") as fh:
fh.write(PREREPAIR_SNIPPET)
def _revert_pyenv(dest):
path = os.path.join(dest, SPARK, "python-env-check.py")
with open(path, "w") as fh:
fh.write(PREREPAIR_PYENV)
def _drift(dest):
with open(os.path.join(dest, SPARK, "snippet.py"), "a") as fh:
fh.write("\n# a sweep appended this line\n")
def _drift_test_file(dest):
"""One byte appended to a Q126 cell: the pin must name it."""
with open(os.path.join(dest, SPARK, "test_counter_util.py"), "a") as fh:
fh.write("\n# a sweep appended this line\n")
def _drop(path_rel):
def _do(dest):
os.remove(os.path.join(dest, SPARK, path_rel))
return _do
# The two pre-repair sources, by value, so a fixture can rebuild the reverted
# tree on any host without a git checkout of 3857b60 / c166bf1.
PREREPAIR_SNIPPET = '''#!/usr/bin/env python3
"""
Snippet: Verify Hermes Agent environment and available tools.
"""
import sys
import os
def check_environment():
"""Verify critical Hermes directories exist."""
dirs = [
"/root/.hermes",
"/root/.hermes/profiles",
"/root/.hermes/skills",
"/root/.hermes/plugins",
]
missing = [d for d in dirs if not os.path.isdir(d)]
return missing
def check_tools():
"""Verify core tools are available."""
tools = ["web_search", "web_extract", "read_file", "write_file"]
available = {}
for tool in tools:
# These are Hermes tools available via hermes_tools
available[tool] = True
return available
if __name__ == "__main__":
missing_dirs = check_environment()
if missing_dirs:
print(f"MISSING_DIRS: {missing_dirs}")
sys.exit(1)
tools = check_tools()
print(f"TOOLS_AVAILABLE: {list(tools.keys())}")
print("Hermes environment verified successfully")
sys.exit(0)'''
# Only the shape that makes the contract leg red is needed: stdlib-only
# packages, no --require, no usage error, green whatever the host has.
PREREPAIR_PYENV = '''#!/usr/bin/env python3
"""Python environment health check - verifies key packages are available."""
import sys
import json
from datetime import datetime, timezone
def check_module(name):
try:
__import__(name)
return {"status": "ok", "available": True}
except ImportError as e:
return {"status": "missing", "available": False, "error": str(e)}
def check_package(name, import_name=None):
import_name = import_name or name
try:
mod = __import__(import_name)
version = getattr(mod, '__version__', 'unknown')
return {"status": "ok", "version": version, "available": True}
except ImportError as e:
return {"status": "missing", "available": False, "error": str(e)}
def main():
results = {
"python": {
"status": "ok",
"version": sys.version,
"executable": sys.executable
},
"packages": {
"json": check_module("json"),
"subprocess": check_module("subprocess"),
"datetime": check_module("datetime"),
}
}
all_ok = all(
p.get("status") == "ok" or p.get("available", False)
for p in results["packages"].values()
)
output = {
"all_ok": all_ok,
"timestamp": datetime.now(timezone.utc).isoformat(),
"components": results
}
print(json.dumps(output, indent=2))
sys.exit(0 if all_ok else 1)
if __name__ == "__main__":
main()'''
def selftest(repo_root):
"""Five one-edit mutants and one control, each its own fixture tree."""
base = tempfile.mkdtemp(prefix="q127-selftest-")
cells = []
def cell(name, repairs, want_rc, want_fragment):
dest = os.path.join(base, name.replace(" ", "-"))
build_fixture(dest, repo_root, repairs)
proc = run([sys.executable, os.path.abspath(__file__), "--root", dest], timeout=600)
out = proc.stdout + proc.stderr
why = []
if proc.returncode != want_rc:
why.append("rc %d, want %d" % (proc.returncode, want_rc))
if want_fragment and want_fragment not in out:
why.append("output does not name %r" % want_fragment)
cells.append((name, proc.returncode, not why))
print("%s %s (rc %d)%s"
% ("ok " if not why else "FAIL", name, proc.returncode,
"" if not why else " -- " + "; ".join(why)))
if why:
print(" " + out.strip().replace("\n", "\n ")[-1200:])
cell("control-clean", lambda dest: None, 0, "closed artifacts are still repaired")
cell("mutant-revert-snippet", _revert_snippet, 1, "is the 3857b60 pre-repair blob")
cell("mutant-revert-pyenv", _revert_pyenv, 1, "is the c166bf1 pre-repair blob")
cell("mutant-drifted-bytes", _drift, 1, "neither the pinned repair")
cell("mutant-artifact-absent", _drop("verify_hermes_setup.sh"), 1, "absent")
cell("mutant-test-file-absent", _drop("test_counter_util.py"), 1,
"Q126's cell is gone")
cell("mutant-test-file-drifted", _drift_test_file, 1, "neither the pinned repair")
# The classifier cells grade the diagnosis the real reverts produced, so the
# "reverted" naming cannot rot into prose. `afb231d` reproduced every one of
# these pre_repair digests in the tree, which is what the digests record.
cases = [
("snippet.py", PINS["snippet.py"]["pre_repair"], "reverted"),
("python-env-check.py", PINS["python-env-check.py"]["pre_repair"], "reverted"),
("snippet.py", PINS["snippet.py"]["repaired"], "repaired"),
("verify_hermes_setup.sh", "0" * 64, "drifted"),
("test_prime_factorizer.py",
PINS["test_prime_factorizer.py"]["pre_repair"], "reverted"),
("test_hermes.py", PINS["test_hermes.py"]["repaired"], "repaired"),
]
bad = [c for c in cases if classify(c[0], c[1]) != c[2]]
cells.append(("classify-revert-digests", 0 if not bad else 1, not bad))
print("%s classify-revert-digests (%d/%d)"
% ("ok " if not bad else "FAIL", len(cases) - len(bad), len(cases)))
shutil.rmtree(base, ignore_errors=True)
passed = sum(1 for c in cells if c[2])
print("selftest: %d cells, %d passed, %d failed" % (len(cells), passed, len(cells) - passed))
return 0 if passed == len(cells) else 1
def main(argv=None):
ap = argparse.ArgumentParser(description="guard the closed repairs of Q109 and Q120")
ap.add_argument("--root", default=None, help="tree to grade (default: this repository)")
ap.add_argument("--no-cells", action="store_true", help="skip the subprocess cell leg")
ap.add_argument("--selftest", action="store_true", help="grade the guard on five mutants")
args = ap.parse_args(argv)
root = args.root or os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
if args.selftest:
return selftest(root)
return guard(root, run_cells=not args.no_cells)
if __name__ == "__main__":
sys.exit(main())