Files
stack/agents/darkwing/work/queue-115/r2/mutants.py
T
jason.woltjeandClaude Opus 5.5 40e5917f9b docs: row 115 round 2 packet, the seen-before-close request test (#1609)
Candidate on base 94ece491f: 5 files, manifest 1910eb5a; only
decision-close.test.mjs changed since round 1. Mutants m1-m4 killed
(m4 is Filbert's f4); gate passed with no flakes.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-14 12:00:51 -05:00

61 lines
2.9 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# Row 115 (#1609): the brief's three mutants of the decision close message.
# m1 drops the message: the close block never runs.
# m2 addresses the message to route_to instead of the raising role.
# m3 drops the raising-run check, so the raiser's own withdraw sends one too.
# Each runs the FULL bus suite (node --test tests/*.test.mjs in packages/bus)
# and restores the source from the .orig copy whatever happens.
# Usage: mutants.py <worktree> <log dir>
import os, shutil, subprocess, sys, signal
WT, LOGS = sys.argv[1], sys.argv[2]
MUTANTS = {
'm1': ('packages/bus/src/broker.mjs',
" if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {\n",
" if (false) {\n"),
'm2': ('packages/bus/src/broker.mjs',
" d.raised_by_role,\n 'DECISION',\n",
" d.route_to,\n 'DECISION',\n"),
'm3': ('packages/bus/src/broker.mjs',
" if (op !== 'seen' && (s.human || s.run !== d.raised_by_run)) {\n",
" if (op !== 'seen') {\n"),
# Filbert's f4 in round 1: the fallback loses its op filter, so a human seen's answer can win.
'm4': ('packages/bus/src/broker.mjs',
" AND json_extract(body,'$.op') IN ('resolved','withdrawn') ORDER BY seq LIMIT 1\",",
" ORDER BY seq LIMIT 1\","),
}
os.makedirs(LOGS, exist_ok=True)
env = dict(os.environ, TMPDIR=os.path.expanduser('~/darkwing-scratch/r115/tmp'))
for name, (rel, cur, new) in MUTANTS.items():
src = os.path.join(WT, rel)
text = open(src).read()
if text.count(cur) != 1:
sys.exit(f'STOP {name}: the target text in {rel} is not unique or has moved')
if os.path.exists(src + '.orig'):
sys.exit(f'STOP {rel}.orig already exists')
log_path = os.path.join(LOGS, f'mutant-{name}.log')
shutil.copy2(src, src + '.orig')
try:
open(src, 'w').write(text.replace(cur, new))
with open(log_path, 'w') as out:
p = subprocess.Popen('node --test tests/*.test.mjs', shell=True, cwd=os.path.join(WT, 'packages/bus'),
stdout=out, stderr=subprocess.STDOUT, env=env, start_new_session=True)
try:
rc = p.wait(timeout=600)
except subprocess.TimeoutExpired:
os.killpg(p.pid, signal.SIGKILL)
p.wait()
rc = 'timeout'
finally:
shutil.move(src + '.orig', src)
restored = open(src).read() == text
log = open(log_path).read()
counts = [l for l in log.splitlines() if l.startswith(('ℹ pass', 'ℹ fail', 'ℹ skipped'))]
failed = [l.strip() for l in log.splitlines() if l.lstrip().startswith('✖') and 'failing tests' not in l]
print(f'mutant {name} rc={rc} {" ".join(counts)} restored={restored}')
for f in sorted(set(failed)):
print(' ' + f)
print('KILLED' if rc != 0 else 'SURVIVED')
if not restored:
sys.exit(f'STOP {rel} was not restored')