Files
troupe-standup/wizard-work/spark/closed_artifact_guard.py
T
volt 1cb001a350 volt: Q127 -- restore Q109/Q120 repairs and pin them by digest
899ce4d put both pre-repair blobs back; both cells read red at HEAD (CHECK-env-verifiers 9 failures, test_python_env_check 5 failures + 2 errors). Restored byte-identically from ebba640 and bda365c. New closed_artifact_guard.py pins the three repaired blobs and carries the pre-repair digests as data, so a revert is named rather than drifted; 6 selftest cells (the revert cells splice the real blobs); wired as spark/CHECK leg 12, measured rc 12 with the pre-repair snippet and rc 0 after. Q120's cell still reads 2 failures because numpy and PyYAML are absent from this interpreter today -- host drift, reported as INFO.
2026-10-30 10:54:34 +00:00

496 lines
18 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
contract:<name> the repaired artifact's documented behaviour, by subprocess
env:<name> INFO only -- host packages the Q120 cell is coupled to
selftest six fixtures: one control, five one-edit mutants
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",
},
}
# 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)
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 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_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 --
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"]:
shutil.copy2(os.path.join(repo_root, SPARK, rel), 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 _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")
# The sixth cell is not a tree: it grades the classifier on the digests the
# real reverts produced, so the "reverted" diagnosis cannot rot into prose.
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"),
]
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())