#!/usr/bin/env python3 # Row 213 (C2-08) mutants across the changed sources. Run from the row213 worktree root, in Sage's slot. # Each mutant changes one anchor (count must be 1), runs the named test files alone, then restores. import os, shutil, subprocess, sys BAK = "/mnt/storage/scratch/morgana-r213/mutant-backups" # any scratch path outside the tree BIZ = "packages/business/src/business.mjs" LS = "packages/business/src/launch-settings.mjs" IS = "packages/business/src/instance-settings.mjs" ROLE = "packages/business/src/role.mjs" BUNDLE = "packages/harness/src/bundle.mjs" TOOLS = "packages/harness/src/tools.mjs" BROKER = "packages/bus/src/broker.mjs" LAUNCHER = "packages/cli/src/launcher.mjs" PI = "adapters/pi/adapter.sh" CLAUDE = "adapters/claude/adapter.sh" T_BIZ = [("business", "tests/business.test.mjs")] T_LS = [("business", "tests/launch-settings.test.mjs")] T_IS = [("business", "tests/instance-settings.test.mjs")] T_ROLE = [("business", "tests/role.test.mjs")] T_HC = [("harness", "tests/creative.test.mjs")] T_BUS = [("bus", "tests/creative.test.mjs")] T_LAUNCH = [("cli", "tests/launcher.test.mjs")] T_ADAPT = [("harness", "tests/creative.test.mjs"), ("harness", "tests/skills.test.mjs")] ABS = ' *) PROMPT_FILE="$(pwd)/$PROMPT_FILE" ;;' MUTANTS = [ # D1 (deviation): persona vars only on a creative definition, both or neither. ("D1a persona vars check not called", BIZ, " checkPersonaVars(out[instance].vars, entry.definition, at);\n", "", T_BIZ + T_IS), ("D1b any definition may carry persona vars", BIZ, " if (!CREATIVE_DEFINITIONS.includes(definition)) refuse(", " if (false) refuse(", T_BIZ + T_IS), ("D1c one persona var alone accepted", BIZ, " if (set.length === 1) refuse(", " if (false) refuse(", T_BIZ), # D2 (deviation): the launcher hands buildBundle the persona directory. ("D2 launcher passes no persona directory", LAUNCHER, ' personaDir: join(configDir(env), "personas"),', " personaDir: null,", T_LAUNCH), # M10: the human's settings. ("L1 models-key change by anyone", LS, " if (was !== is) creativeHumanOnly(", " if (false) creativeHumanOnly(", T_LS), ("L2 fallback.model key not checked", LS, ' for (const key of [vars.model, vars["fallback.model"]]) {', " for (const key of [vars.model]) {", T_LS), ("L3 instances key allowed for a creative instance", LS, " if (CREATIVE_DEFINITIONS.includes(next.roles[instance].definition)) {\n throw new OrganizationError(\"organization-creative-instance\"", " if (false) {\n throw new OrganizationError(\"organization-creative-instance\"", T_LS), ("L4 setRoutes by anyone", LS, " creativeHumanOnly(old.document, actor, instance, next.roles[instance].definition);", "", T_LS), ("L5 creativeHumanOnly never refuses", LS, " if (CREATIVE_DEFINITIONS.includes(definition) && actor !== document.human) {", " if (false) {", T_LS + T_IS), ("I1 add of a creative instance by anyone", IS, " creativeHumanOnly(old, actor, instance, args.definition);\n definitionFor(rolesDir, args.definition);\n next.roles[instance] =", " definitionFor(rolesDir, args.definition);\n next.roles[instance] =", T_IS), ("I2 change of a creative instance by anyone", IS, " creativeHumanOnly(old, actor, instance, entry.definition);\n", "", T_IS), ("I3 redefine to creative by anyone", IS, " creativeHumanOnly(old, actor, instance, args.definition);\n", "", T_IS), # M1: role files. ("R1 checkCreative not called", ROLE, "CREATIVE_DEFINITIONS.includes(role.name) ? checkCreative(role) : role", "role", T_ROLE), ("R2 wider tools accepted", ROLE, " if (wider.length > 0) refuse(", " if (false) refuse(", T_ROLE), ("R3 non-human messaging accepted", ROLE, ' if (role.messaging[key].some((entry) => entry !== "human")) {', " if (false) {", T_ROLE), # Persona file and block. ("B1 group/other mode bits accepted", BUNDLE, " if (stat.mode & 0o077) throw", " if (false) throw", T_HC), ("B2 digest not checked", BUNDLE, " if (sha256(data) !== digest) {", " if (false) {", T_HC), ("B3 headings and end tag accepted", BUNDLE, " if (text.includes(PERSONA_END) || /^##? /m.test(text)) {", " if (false) {", T_HC), ("B4 symlink followed", BUNDLE, "constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK", "constants.O_RDONLY | constants.O_NONBLOCK", T_HC), ("B5 character cap off", BUNDLE, " if ([...text].length > PERSONA_CHARS) throw", " if (false) throw", T_HC), ("B6 persona block dropped", BUNDLE, " ...personaSection(resolved, persona),\n", "", T_HC), ("B7 no personaDir with a digest accepted", BUNDLE, " if (digest === undefined) return null;\n throw new BundleError(", " return null;\n throw new BundleError(", T_HC), ("B8 creative role on harness-builtin web", BUNDLE, ' if (granted && source === "harness-builtin" && CREATIVE_DEFINITIONS.includes(resolved.definition)) {', " if (false) {", T_HC), # M6. ("T1 raise_decision offered to creative roles", TOOLS, ' if (!CREATIVE_DEFINITIONS.includes(resolved.definition)) add("raise_decision"', ' add("raise_decision"', T_HC), ("K1 broker creative raise check off", BROKER, " if (request.verb === 'decision.raise' && !s.human && !s.tester &&\n CREATIVE_DEFINITIONS", " if (false &&\n CREATIVE_DEFINITIONS", T_BUS), # B1 (webby-0040): the broker names the writer's actor from the session. ("K2 chief session reaches the writer as the human", BROKER, 'actor = this.#business(s.business).roles[s.role].definition + "." + s.role;', 'actor = "jason";', T_BUS), ("K3 tester session reaches the writer as the human", BROKER, 'let actor = s.tester ? "tester" : s.human;', 'let actor = s.tester ? "jason" : s.human;', T_BUS), ("V1 persona.name line separators accepted (N3)", "packages/business/src/vars.mjs", "\\u007f\\u2028\\u2029]", "\\u007f]", T_BIZ), # Prompt by file (adapters). ("A1 pi prompt check without -f", PI, '[ -f "$PROMPT_FILE" ] && [ -r "$PROMPT_FILE" ] ||', '[ -r "$PROMPT_FILE" ] ||', T_ADAPT), ("A2 pi relative prompt path kept", PI, ABS, " *) ;;", T_ADAPT), ("A3 claude relative prompt path kept", CLAUDE, ABS, " *) ;;", T_ADAPT), ] os.makedirs(BAK, exist_ok=True) files = sorted({m[1] for m in MUTANTS}) orig = {} for f in files: bak = os.path.join(BAK, f.replace("/", "__")) shutil.copyfile(f, bak) orig[f] = open(bak).read() if orig[f] != open(f).read(): sys.exit(f"backup mismatch for {f}, abort") for name, f, a, b, _ in MUTANTS: if orig[f].count(a) != 1: sys.exit(f"{name}: anchor count {orig[f].count(a)}, abort") killed = 0 try: for name, f, a, b, tests in MUTANTS: open(f, "w").write(orig[f].replace(a, b)) failing = [] for pkg, t in tests: p = subprocess.run(["node", "--test", "--test-reporter=tap", t], cwd=f"packages/{pkg}", capture_output=True, text=True, timeout=600) failing += [f"{pkg}/{t.split('/')[-1]}: {l[7:]}" for l in p.stdout.splitlines() if l.startswith("not ok")] open(f, "w").write(orig[f]) status = "KILLED" if failing else "SURVIVED" killed += bool(failing) print(f"{status} {name}", flush=True) for line in failing: print(f" {line}", flush=True) finally: for f in files: open(f, "w").write(orig[f]) print(f"killed {killed}/{len(MUTANTS)}; restored: {all(open(f).read() == orig[f] for f in files)}")