Candidate on 5661bd233: GATED and GATED_ONLY become PRD 1.5's eight hard
waits (data.delete, network.expose and account.create are new), derived
as ACTIONS minus an explicit ROUTED list (REQ-WAIT-3). A gated raise
citing the current spec goes to the domain arbiter unless it is a hard
wait. land-work refuses an arbiter-resolved merge with not-human.
Mutants 5/5 killed; gate green (bus, business and every scripts/test-*.sh).
Row 137 has not landed, so its REQ-LAND-1 refusal and the host-op
arbiter acceptance are left out, as BUILD.md says.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
51 lines
2.8 KiB
Python
51 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
# Row 139 mutants (slot sage-0518). Each mutates one file, runs the owning suite in its own
|
|
# process group (killpg on timeout), restores the file and checks its hash before the next.
|
|
import hashlib, os, signal, subprocess, sys, time
|
|
|
|
W = "/home/jwoltje/dewey-scratch/r139/wt"
|
|
O = "/home/jwoltje/dewey-scratch/r139/out/mutants"
|
|
os.makedirs(O, exist_ok=True)
|
|
ENV = dict(os.environ, TMPDIR="/home/jwoltje/dewey-scratch/tmp", COMPOSE_PROJECT_NAME="dewey-r139")
|
|
SUITES = {"bus": ["bash", "-c", "cd packages/bus && npm test"],
|
|
"business": ["bash", "-c", "cd packages/business && npm test"]}
|
|
MUTANTS = [
|
|
("m1-no-spec-check", "packages/bus/src/broker.mjs",
|
|
"(cls === 'gated' && spec !== null && !GATED.has(a.action))", "(cls === 'gated' && !GATED.has(a.action))", "bus"),
|
|
("m2-no-hard-wait-check", "packages/bus/src/broker.mjs",
|
|
"(cls === 'gated' && spec !== null && !GATED.has(a.action))", "(cls === 'gated' && spec !== null)", "bus"),
|
|
("m3-new-action-not-hard-wait", "packages/bus/src/broker.mjs",
|
|
"const ROUTED = new Set([\n", "const ROUTED = new Set([\n 'account.create',\n", "bus"),
|
|
("m4-hard-wait-routed", "packages/business/src/vocabulary.mjs",
|
|
"export const ROUTED = Object.freeze([\n", "export const ROUTED = Object.freeze([\n \"spend\",\n", "business"),
|
|
("m5-validator-refusal", "packages/business/src/role.mjs",
|
|
"if (GATED_ONLY.includes(action)) refuse(", "if (false && GATED_ONLY.includes(action)) refuse(", "business"),
|
|
]
|
|
sha = lambda p: hashlib.sha256(open(p, "rb").read()).hexdigest()
|
|
summary = open(os.path.join(O, "summary.txt"), "w")
|
|
for name, rel, old, new, suite in MUTANTS:
|
|
path = os.path.join(W, rel)
|
|
orig = open(path, "rb").read()
|
|
h0 = sha(path)
|
|
text = orig.decode()
|
|
if text.count(old) != 1:
|
|
line = f"{name} SKIP anchor count {text.count(old)}"
|
|
print(line); summary.write(line + "\n"); summary.flush(); sys.exit(3)
|
|
open(path, "w").write(text.replace(old, new))
|
|
assert sha(path) != h0
|
|
start = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
with open(os.path.join(O, f"{name}.txt"), "w") as out:
|
|
p = subprocess.Popen(SUITES[suite], cwd=W, env=ENV, stdout=out, stderr=subprocess.STDOUT, start_new_session=True)
|
|
try:
|
|
rc = p.wait(timeout=600)
|
|
except subprocess.TimeoutExpired:
|
|
os.killpg(p.pid, signal.SIGKILL); p.wait(); rc = "timeout"
|
|
open(path, "wb").write(orig)
|
|
restored = sha(path) == h0
|
|
end = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
|
|
verdict = "KILLED" if rc not in (0, "timeout") else ("TIMEOUT" if rc == "timeout" else "SURVIVED")
|
|
line = f"{name} {rel} suite={suite} rc={rc} {verdict} restored={restored} start={start} end={end}"
|
|
print(line); summary.write(line + "\n"); summary.flush()
|
|
if not restored:
|
|
sys.exit(4)
|