318 lines
14 KiB
Python
318 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""q57_mutation.py -- the committed negative control for check.py.
|
|
|
|
Exit code IS the verdict: 0 = the gate behaves the way its own docstring says on
|
|
a mutated entry, 1 = it does not. Nothing here is asserted; every cell is a run.
|
|
|
|
python3 wizard-work/null/q57_mutation.py
|
|
|
|
WHY A FIXTURE, AND NOT THE LIVE TREE
|
|
`python3 check.py` reads whatever is on disk, so running it in the shared
|
|
checkout grades the worktree and calls the result the commit -- Timmy's own
|
|
first pass on this artifact made exactly that mistake (reply 06). This
|
|
harness never touches the live tree. It takes a `git clone --bare` snapshot
|
|
of it, clones THAT, and mutates only the clone. origin/main inside the
|
|
fixture is the frozen bare repo, so a concurrent push by another wizard
|
|
cannot change what the test is measuring.
|
|
|
|
WHERE THE REPO IS
|
|
Resolved from this file's own path (`git rev-parse --show-toplevel`), so the
|
|
copy in a clone grades that clone. It used to default to the host path
|
|
/root/troupe-standup, which meant a clone's copy silently snapshotted the
|
|
host instead of the tree it sat in -- Q62's class, measured 2026-10-25.
|
|
NULL_REPO=<path> overrides it, and the path actually used is printed.
|
|
|
|
WHAT IT MEASURES -- eight cases, expectations computed here, never hardcoded
|
|
case 0 clean clone -> rc 0, ALL CHECKS GREEN
|
|
case 1 one byte inside a work-file bullet -> rc 1, names that line
|
|
case 2 the operator line -> rc 1, names that line
|
|
case 3 the base-commit line -> rc 1, names that line
|
|
case 4 one line appended -> rc 1, names the line past the end
|
|
case 5 final newline becomes CRLF -> rc 1, "a line terminator differs",
|
|
and NO line number is invented
|
|
case 6 replay in the clone -> the clone's entry is byte-identical,
|
|
its mtime is untouched, `git status`
|
|
is empty, and the HOST's entry is
|
|
untouched (self-location)
|
|
case 7 the published gate moved on -> rc 1, names check.py, while the
|
|
entry check still PASSES
|
|
|
|
Case 5 was GREEN before this artifact. Both sides of the old comparison ran
|
|
under universal-newline translation (subprocess text=True and open(...,
|
|
encoding=...)), so the CRLF never reached the comparison: a doctored entry
|
|
read "byte-identical". Case 7 was impossible before: nothing compared the
|
|
gate on disk with the gate on main, so an entry could be certified by a
|
|
private build. Case 6 was added 2026-10-25: before it, the builder carried
|
|
REPO and HERE as constants, so `python3 wizard-work/null/wizard.py` in a
|
|
clone rewrote the HOST's entry and left the clone alone -- a replay that
|
|
reproduces nothing is not a replay.
|
|
|
|
Expected line numbers are computed two independent ways and both must agree:
|
|
by locating the mutated text in the graded bytes, and by a first-difference
|
|
scan over `graded.split(b"\\n")`. Neither reads the gate's own message.
|
|
|
|
SET NULL_Q57_KEEP=1 to keep the fixture directory for inspection.
|
|
"""
|
|
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def _toplevel(start):
|
|
"""The repository root containing `start`, or "" when there is none."""
|
|
try:
|
|
r = subprocess.run(["git", "-C", start, "rev-parse", "--show-toplevel"],
|
|
capture_output=True, text=True)
|
|
except OSError:
|
|
return ""
|
|
return r.stdout.strip() if r.returncode == 0 else ""
|
|
|
|
|
|
REPO = os.environ.get("NULL_REPO") or _toplevel(HERE)
|
|
LOGREL = "wizard-work/null/wizard.log.md"
|
|
GATEREL = "wizard-work/null/check.py"
|
|
BUILDER = "wizard-work/null/wizard.py"
|
|
PASSED, FAILED = [], []
|
|
|
|
|
|
def sh(args):
|
|
return subprocess.run(args, capture_output=True)
|
|
|
|
|
|
def git(root, *args):
|
|
return sh(["git", "-C", root] + list(args))
|
|
|
|
|
|
def check(name, ok, detail=""):
|
|
ok = bool(ok)
|
|
(PASSED if ok else FAILED).append(name)
|
|
print(("PASS " if ok else "FAIL ") + name + (" -> " + detail if detail else ""))
|
|
return ok
|
|
|
|
|
|
def line_of(lines, needle):
|
|
"""1-based number of the line containing `needle`, or 0 if it is absent."""
|
|
for i, l in enumerate(lines):
|
|
if needle in l:
|
|
return i + 1
|
|
return 0
|
|
|
|
|
|
def first_diff_scan(want, got):
|
|
"""Independent first-difference scan over raw bytes split on b'\\n'."""
|
|
w, g = want.split(b"\n"), got.split(b"\n")
|
|
for i in range(max(len(w), len(g))):
|
|
a = w[i] if i < len(w) else None
|
|
b = g[i] if i < len(g) else None
|
|
if a != b:
|
|
return i + 1
|
|
return None
|
|
|
|
|
|
def main():
|
|
print("grading repo: %s" % (REPO or "<none: this file is not in a git checkout>"))
|
|
if not REPO:
|
|
print("CANNOT GRADE: no repository root from %s (set NULL_REPO)" % HERE)
|
|
return 2
|
|
root = tempfile.mkdtemp(prefix="q57-mutation-")
|
|
bare = os.path.join(root, "origin.git")
|
|
fx = os.path.join(root, "fixture")
|
|
logp = os.path.join(fx, LOGREL)
|
|
gatep = os.path.join(fx, GATEREL)
|
|
print("fixture root: %s" % root)
|
|
|
|
r = sh(["git", "clone", "--bare", "--quiet", REPO, bare])
|
|
if r.returncode != 0:
|
|
print("CANNOT GRADE cannot snapshot the repo: "
|
|
+ r.stderr.decode("utf-8", "replace")[:200])
|
|
return 2
|
|
r = sh(["git", "clone", "--quiet", bare, fx])
|
|
if r.returncode != 0:
|
|
print("CANNOT GRADE cannot clone the fixture: "
|
|
+ r.stderr.decode("utf-8", "replace")[:200])
|
|
return 2
|
|
|
|
def run_gate():
|
|
p = subprocess.run([sys.executable, gatep], capture_output=True, text=True)
|
|
return p.returncode, (p.stdout or "") + (p.stderr or "")
|
|
|
|
graded = git(fx, "show", "origin/main:" + LOGREL).stdout
|
|
text = graded.decode("utf-8")
|
|
lines = text.splitlines()
|
|
if not graded or not lines:
|
|
print("FAIL the fixture has no graded entry to mutate")
|
|
return 1
|
|
|
|
# ---- case 0: the fixture is clean and the gate is green on it ---------
|
|
disk = open(logp, "rb").read()
|
|
pre = check("fixture precondition: disk entry == the blob the gate grades",
|
|
disk == graded, "%d bytes" % len(graded))
|
|
rc, out = run_gate()
|
|
check("case 0 clean clone exits 0", rc == 0 and "ALL CHECKS GREEN" in out,
|
|
"rc %d, last line %r" % (rc, out.strip().splitlines()[-1] if out.strip() else ""))
|
|
if not pre:
|
|
print("\nFAILED: the fixture is not clean; no mutation result is meaningful")
|
|
return 1
|
|
|
|
def reset_log():
|
|
with open(logp, "wb") as fh:
|
|
fh.write(graded)
|
|
|
|
def case(label, mutated, expect_line, needles=()):
|
|
"""Write `mutated`, run the gate, and require it to name `expect_line`."""
|
|
with open(logp, "wb") as fh:
|
|
fh.write(mutated)
|
|
rc, out = run_gate()
|
|
named = re.search(r"line (\d+) differs", out)
|
|
ok = (rc == 1
|
|
and "ALL CHECKS GREEN" not in out
|
|
and expect_line is not None
|
|
and named is not None
|
|
and int(named.group(1)) == expect_line
|
|
and all(n in out for n in needles))
|
|
check(label, ok,
|
|
"rc %d, gate named line %s, expected %s"
|
|
% (rc, named.group(1) if named else "nothing", expect_line))
|
|
if not ok:
|
|
print(" gate output:\n" + "\n".join(" " + l for l in out.strip().splitlines()))
|
|
return ok
|
|
|
|
# ---- case 1: one byte inside a data line -----------------------------
|
|
needle1 = b"arguments, not the clock"
|
|
i1 = line_of(lines, needle1.decode())
|
|
m1 = list(graded.split(b"\n"))
|
|
m1[i1 - 1] = m1[i1 - 1].replace(b"arguments", b"argumentz")
|
|
mutated1 = b"\n".join(m1)
|
|
check("case 1 expectation cross-check: needle line == byte-scan line",
|
|
i1 > 0 and i1 == first_diff_scan(graded, mutated1), "line %d" % i1)
|
|
case("case 1 one byte inside a work-file bullet is named by line",
|
|
mutated1, i1, needles=("graded=", "disk="))
|
|
|
|
# ---- case 2: the operator line ---------------------------------------
|
|
reset_log()
|
|
needle2 = b"- operator: "
|
|
i2 = line_of(lines, needle2.decode())
|
|
m2 = list(graded.split(b"\n"))
|
|
m2[i2 - 1] = m2[i2 - 1].replace(b"null", b"nobody-at-all")
|
|
mutated2 = b"\n".join(m2)
|
|
check("case 2 expectation cross-check: needle line == byte-scan line",
|
|
i2 > 0 and i2 == first_diff_scan(graded, mutated2), "line %d" % i2)
|
|
case("case 2 a doctored operator line is named by line", mutated2, i2)
|
|
|
|
# ---- case 3: the base-commit line ------------------------------------
|
|
reset_log()
|
|
needle3 = b"- base commit: "
|
|
i3 = line_of(lines, needle3.decode())
|
|
m3 = list(graded.split(b"\n"))
|
|
m3[i3 - 1] = re.sub(rb"(?<=- base commit: )[0-9a-f]+", b"deadbeef", m3[i3 - 1])
|
|
mutated3 = b"\n".join(m3)
|
|
check("case 3 expectation cross-check: needle line == byte-scan line",
|
|
i3 > 0 and i3 == first_diff_scan(graded, mutated3), "line %d" % i3)
|
|
case("case 3 a doctored base commit is named by line", mutated3, i3)
|
|
|
|
# ---- case 4: one line appended ---------------------------------------
|
|
reset_log()
|
|
mutated4 = graded + b"- extra: a line no writer produced\n"
|
|
i4 = len(lines) + 1
|
|
check("case 4 expectation cross-check: appended line == byte-scan line",
|
|
i4 == first_diff_scan(graded, mutated4), "line %d" % i4)
|
|
case("case 4 an appended line is named, and the graded side reads <no such line>",
|
|
mutated4, i4, needles=("<no such line>",))
|
|
|
|
# ---- case 5: a line terminator, and no line to name -------------------
|
|
reset_log()
|
|
mutated5 = graded[:-1] + b"\r\n"
|
|
with open(logp, "wb") as fh:
|
|
fh.write(mutated5)
|
|
rc, out = run_gate()
|
|
check("case 5 a CRLF mutation is a failure, not a PASS",
|
|
rc == 1 and "ALL CHECKS GREEN" not in out, "rc %d" % rc)
|
|
check("case 5 every line is equal, so no line number is invented",
|
|
"line terminator" in out and re.search(r"line \d+ differs", out) is None,
|
|
"reported as a terminator")
|
|
check("case 5 the byte count is printed, so the 1-byte delta is visible",
|
|
"%d bytes" % len(graded) in out and "%d bytes" % len(mutated5) in out)
|
|
|
|
# ---- case 6: a replay in the clone reproduces it and touches nothing ---
|
|
# Before 2026-10-25 the builder held REPO and HERE as constants, so this
|
|
# exact run rewrote the HOST's entry and left the clone alone: mtime moved
|
|
# at /root/troupe-standup, the clone's file never opened. Both halves are
|
|
# measured here -- the clone's bytes and mtime, and the host's.
|
|
reset_log()
|
|
host_log = os.path.join(REPO, LOGREL)
|
|
clone_before = open(logp, "rb").read()
|
|
mtime_before = os.stat(logp).st_mtime_ns
|
|
host_before = open(host_log, "rb").read()
|
|
host_mtime = os.stat(host_log).st_mtime_ns
|
|
p = subprocess.run([sys.executable, os.path.join(fx, BUILDER)], cwd=fx,
|
|
capture_output=True, text=True)
|
|
clone_after = open(logp, "rb").read()
|
|
mtime_after = os.stat(logp).st_mtime_ns
|
|
host_after = open(host_log, "rb").read()
|
|
status = git(fx, "status", "--porcelain").stdout.decode("utf-8", "replace").strip()
|
|
check("case 6 the fixture's builder exits 0", p.returncode == 0,
|
|
"rc %d" % p.returncode)
|
|
check("case 6 the replay reproduces the entry byte-for-byte",
|
|
clone_after == clone_before,
|
|
"%d bytes" % len(clone_after) if clone_after == clone_before
|
|
else "clone entry changed: %d -> %d bytes" % (len(clone_before), len(clone_after)))
|
|
check("case 6 the replay opens no file for writing (mtime unchanged)",
|
|
mtime_after == mtime_before,
|
|
"mtime %d" % mtime_after if mtime_after == mtime_before
|
|
else "mtime moved %d -> %d" % (mtime_before, mtime_after))
|
|
check("case 6 the worktree is clean after the replay", status == "",
|
|
"clean" if status == "" else status.splitlines()[0])
|
|
check("case 6 the HOST's entry is untouched: the builder grades its own checkout",
|
|
host_after == host_before and os.stat(host_log).st_mtime_ns == host_mtime,
|
|
"host %s untouched" % LOGREL
|
|
if host_after == host_before and os.stat(host_log).st_mtime_ns == host_mtime
|
|
else "the host's entry was written by a clone-side replay")
|
|
|
|
# ---- case 7: the published gate has moved on --------------------------
|
|
reset_log()
|
|
m = re.search(r"^- base commit: ([0-9a-f]{40})$", text, re.M)
|
|
if not m:
|
|
check("case 7 the entry names a 40-hex base commit", False, "no base commit line")
|
|
else:
|
|
named = m.group(1)
|
|
with open(gatep, "ab") as fh:
|
|
fh.write(b"\n# fixture: a later commit on main changed this file\n")
|
|
git(fx, "-c", "user.name=null", "-c", "user.email=null@agents.invalid",
|
|
"commit", "-aqm", "fixture: publish a gate that moved on")
|
|
push = git(fx, "push", "--quiet", "origin", "HEAD:main")
|
|
check("case 7 fixture: the frozen origin advanced past the named commit",
|
|
push.returncode == 0,
|
|
"origin/main is now %s" % git(fx, "rev-parse", "HEAD").stdout.decode().strip()[:12])
|
|
# Put the GRADED gate back on disk: disk == named commit's blob (so the
|
|
# named-commit check still passes) but != the published blob.
|
|
with open(gatep, "wb") as fh:
|
|
fh.write(git(fx, "show", "%s:%s" % (named, GATEREL)).stdout)
|
|
rc, out = run_gate()
|
|
check("case 7 a gate that main does not carry is refused",
|
|
rc == 1 and "FAIL the gate on disk is the gate published on the remote main" in out,
|
|
"rc %d" % rc)
|
|
check("case 7 isolation: the entry itself is still byte-identical on main",
|
|
"FAIL entry bytes are the blob on the remote main" not in out
|
|
and "PASS entry bytes are the blob on the remote main" in out,
|
|
"only the published-gate cell moved")
|
|
|
|
print("\n%d passed, %d failed" % (len(PASSED), len(FAILED)))
|
|
if os.environ.get("NULL_Q57_KEEP") != "1":
|
|
shutil.rmtree(root, ignore_errors=True)
|
|
else:
|
|
print("fixture kept at %s" % root)
|
|
if FAILED:
|
|
print("FAILED: " + ", ".join(FAILED))
|
|
return 1
|
|
print("MUTATION CONTROL GREEN")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|