Haiku subagent build under decision 129. Candidate manifest 9ac3a56e names packages/tasks/src/fake.mjs and packages/tasks/tests/tree.test.mjs. Gate and both named mutants ran in Sage's slot. Co-Authored-By: Claude Opus 5.5 <[email protected]>
96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Row 71 round 1 mutants, run from the worktree root after gate.sh passes.
|
|
|
|
Each mutant is one edit to packages/tasks/src/sync.mjs, applied alone, followed by
|
|
the tasks suite. The original bytes are held in memory and written back in a
|
|
finally block, and the sha256 is checked after the last restore.
|
|
A mutant is killed when the suite exits nonzero. A 124 exit is a timeout and is
|
|
reported as such, not as a kill.
|
|
"""
|
|
import datetime
|
|
import hashlib
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
SYNC = 'packages/tasks/src/sync.mjs'
|
|
P = 'agents/researcher/work/queue-71/r1'
|
|
OUT = P + '/out'
|
|
ORIG_SHA = '35e79dc2ac56af8047bef11a5aaf5f1d37ce3e11513f9968454053068f59ebcf'
|
|
LINE = ' return r.status === 403 || r.status === 404;'
|
|
MUTANTS = [
|
|
# Filbert N3: the confirm read treats any 4xx as "left".
|
|
('r3-confirm-4xx', LINE, ' return r.status >= 400 && r.status < 500;'),
|
|
# Dewey: the confirm read's failed call (status 0) counts as "left".
|
|
('r3-confirm-error-on-throw', LINE, ' return r.status === 403 || r.status === 404 || r.status === 0;'),
|
|
]
|
|
|
|
|
|
def stamp():
|
|
return datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
|
|
|
|
|
|
def sha(path):
|
|
with open(path, 'rb') as f:
|
|
return hashlib.sha256(f.read()).hexdigest()
|
|
|
|
|
|
def write(path, data):
|
|
with open(path, 'wb') as f:
|
|
f.write(data)
|
|
|
|
|
|
def run_suite(name):
|
|
log = f'{OUT}/mutant-{name}.log'
|
|
cmd = ['env', 'DOCKER_HOST=unix:///nonexistent.sock', 'node', '--test', '--test-reporter=tap', 'packages/tasks/tests/*.test.mjs']
|
|
rc = subprocess.run(['python3', P + '/run_group.py', '900', log, os.getcwd(), '--'] + cmd).returncode
|
|
with open(log, encoding='utf-8', errors='replace') as f:
|
|
text = f.read()
|
|
failing = []
|
|
for m in re.finditer(r'^\s*not ok \d+ - (.+)$', text, re.M):
|
|
name_ = m.group(1).strip()
|
|
if name_ not in failing:
|
|
failing.append(name_)
|
|
counts = ' '.join(re.findall(r'^# (?:tests|pass|fail) \d+', text, re.M))
|
|
return rc, failing, counts
|
|
|
|
|
|
def main():
|
|
if not os.path.isfile(SYNC):
|
|
sys.exit('run from the worktree root')
|
|
os.makedirs(OUT, exist_ok=True)
|
|
original = open(SYNC, 'rb').read()
|
|
if hashlib.sha256(original).hexdigest() != ORIG_SHA:
|
|
sys.exit('sync.mjs is not the candidate copy; refusing to mutate it')
|
|
text = original.decode('utf-8')
|
|
summary = []
|
|
try:
|
|
for name, old, new in MUTANTS:
|
|
if text.count(old + '\n') != 1:
|
|
sys.exit(f'{name}: target line must occur exactly once')
|
|
mutated = text.replace(old + '\n', new + '\n').encode('utf-8')
|
|
write(SYNC, mutated)
|
|
t0 = stamp()
|
|
rc, failing, counts = run_suite(name)
|
|
t1 = stamp()
|
|
write(SYNC, original)
|
|
if sha(SYNC) != ORIG_SHA:
|
|
sys.exit(f'{name}: restore did not reproduce the original sha256')
|
|
status = 'killed' if rc not in (0, 124) else ('timeout' if rc == 124 else 'SURVIVED')
|
|
summary.append(f'{name} {status} rc={rc} start={t0} end={t1} {counts}')
|
|
for n in failing:
|
|
summary.append(f' not ok: {n}')
|
|
finally:
|
|
write(SYNC, original)
|
|
final = sha(SYNC)
|
|
summary.append(f'restored {SYNC} sha256 {final} {"OK" if final == ORIG_SHA else "MISMATCH"}')
|
|
with open(OUT + '/mutants-summary.txt', 'w', encoding='utf-8') as f:
|
|
f.write('\n'.join(summary) + '\n')
|
|
print('\n'.join(summary))
|
|
sys.exit(0 if final == ORIG_SHA else 1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|