#!/usr/bin/env python3 """R7 mutants. Run only on Sage CLEAR in Lena's isolated candidate. Every replacement must occur once. Product bytes are restored even on timeout or failure. A killed mutant must contain the named assertion in the script's output; startup errors and timeout alone are not evidence. The shell owns its own timeout and EXIT/TERM cleanup. Do not put a killing Python timeout around it. """ from pathlib import Path import subprocess import sys repo = Path(__file__).resolve().parents[4] mutants = { 'M1': ( 'packages/cli/src/access.mjs', 'projects: resolveProjects(list(parsed.values["--projects"]), rows),', 'projects: ["*"],', 'R7 ungranted project must not appear', ), 'M2': ( 'packages/bus/src/broker.mjs', "this.#claimEnd({ business: t.business }, h, 'revoke', null, 'access-revoked', by);", "/* R7 mutant: skip ending the revoked claim */", 'R7 revoke must end the claim as access-revoked', ), } if len(sys.argv) != 2 or sys.argv[1] not in mutants: raise SystemExit('usage: mutants.py M1|M2') name = sys.argv[1] file, before, after, assertion = mutants[name] path = repo / file original = path.read_bytes() text = original.decode() if text.count(before) != 1: raise SystemExit(f'{name}: anchor count {text.count(before)}, expected 1') try: path.write_text(text.replace(before, after)) result = subprocess.run(['scripts/test-federation-acceptance.sh'], cwd=repo, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) print(result.stdout, end='') if result.returncode == 0 or assertion not in result.stdout: raise SystemExit(f'{name}: NOT KILLED by expected assertion') if 'R7 cleanup: no child groups, ports or sockets remain; no units created' not in result.stdout: raise SystemExit(f'{name}: cleanup not verified') print(f'{name}: KILLED by {assertion}') finally: path.write_bytes(original) if path.read_bytes() != original: raise SystemExit(f'{name}: restore failed')