Files
jason.woltjeandClaude Sonnet 5.5 f758a8e927 researcher: row 120 round 2 packet (review paths gated, rebased on 66ea40580)
Candidate diff and manifest against 66ea40580; messaging suite now 12 tests;
gate green; 21/21 mutants caught (m16, m17 added). No push.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-14 20:58:25 -05:00

94 lines
5.2 KiB
Bash
Executable File
Raw Permalink 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 bash
# Row 120 (#1614): the broker limits who the assistant sends to and hears from.
# Each check must fail on a mutant. For each mutant this runs one named test file against the
# real source (control) and against the source with one exact-once text edit, in the given worktree,
# one test file at a time, and restores every source file on exit.
# Usage: mutants.sh <worktree> <outdir>
set -u
WT=$(cd "$1" && pwd); OUT=$(mkdir -p "$2" && cd "$2" && pwd)
cd "$WT" || exit 2
BAK=$(mktemp -d "$OUT/.bak.XXXXXX")
declare -A SAVED
save() { [ -n "${SAVED[$1]:-}" ] || { mkdir -p "$BAK/$(dirname "$1")"; cp "$1" "$BAK/$1"; SAVED[$1]=1; }; }
restore() { for f in "${!SAVED[@]}"; do cp "$BAK/$f" "$f"; done; rm -rf "$BAK"; }
trap restore EXIT
edit() { # file old new: exact-once replace
save "$1"
node -e '
const fs = require("fs"); const [f, a, b] = process.argv.slice(1);
const s = fs.readFileSync(f, "utf8"); const n = s.split(a).length - 1;
if (n !== 1) { console.error("expected one match, found " + n); process.exit(3); }
fs.writeFileSync(f, s.replace(a, () => b));
' "$1" "$2" "$3"
}
run() { # name testfile
setsid -w timeout -k 10 400 node --test "$2" > "$OUT/$1.out" 2>&1; echo "$?" > "$OUT/$1.rc"
echo "$1: exit $(cat "$OUT/$1.rc"); $(grep -E '^ℹ (tests|pass|fail)' "$OUT/$1.out" | tr '\n' ' ')"
grep -E '^✖' "$OUT/$1.out" | sort -u | sed 's/ ([0-9.]*ms)$//' | sed "s/^/ failed: /"
}
mutant() { # name testfile file old new
edit "$3" "$4" "$5" || { echo "$1: edit failed"; return; }
diff -u "$BAK/$3" "$3" > "$OUT/$1.patch"
run "$1" "$2"
cp "$BAK/$3" "$3"
}
BT=packages/business/tests; MSG=packages/bus/tests/messaging.test.mjs
ROLE=$BT/role.test.mjs; AST=$BT/assistant.test.mjs
B=packages/bus/src/broker.mjs; R=packages/business/src/role.mjs; A=roles/assistant.json
for t in "$MSG" "$ROLE" "$AST"; do run "control-$(basename "$t" .test.mjs)" "$t"; done
# m1: drop the sender-side check (the sender's sendTo)
mutant m1-drop-sender-side-check "$MSG" "$B" "if (out && !out.sendTo.includes(target)) fail('message-recipient-refused');" "if (false) fail('message-recipient-refused');"
# m2: drop the target-side check (the target's receiveFrom)
mutant m2-drop-target-side-check "$MSG" "$B" "if (inbound && !inbound.receiveFrom.includes(sender)) fail('message-sender-refused');" "if (false) fail('message-sender-refused');"
# m3: drop the refusal log for these two refusals
mutant m3-drop-refusal-log "$MSG" "$B" "const error = e instanceof BusError ? e : new BusError(other);" "const error = e instanceof BusError ? e : new BusError(other);
if (error.code.startsWith('message-')) return error;"
# m4, m5: each direction reads the other direction's list
mutant m4-sender-reads-receive-list "$MSG" "$B" "out.sendTo.includes(target)" "out.receiveFrom.includes(target)"
mutant m5-target-reads-send-list "$MSG" "$B" "inbound.receiveFrom.includes(sender)" "inbound.sendTo.includes(sender)"
# m6: the tester is exempt from the target's receive list
mutant m6-tester-exempt "$MSG" "$B" "if (inbound && !inbound.receiveFrom" "if (inbound && !s.tester && !inbound.receiveFrom"
# m7, m8: a role without the field is refused instead of unchanged (each direction)
mutant m7-no-field-refuses-sender "$MSG" "$B" "definition(s.role)?.messaging;" "definition(s.role)?.messaging ?? { sendTo: [], receiveFrom: [] };"
mutant m8-no-field-refuses-target "$MSG" "$B" "definition(to)?.messaging;" "definition(to)?.messaging ?? { sendTo: [], receiveFrom: [] };"
# m9: the lists are matched on instance names, not definition names
mutant m9-target-by-instance-name "$MSG" "$B" ": b.roles[to]?.definition;" ": to;"
# m10: the constructor stops checking the shape
mutant m10-boot-shape-unchecked "$MSG" "$B" "fail('invalid-messaging');" "void 0;"
# m11: the validator accepts the tester in a list
mutant m11-validator-accepts-tester "$ROLE" "$R" 'if (entry === "tester") refuse(' 'if (false) refuse('
# m12: the validator drops the field from the loaded role
for pair in "a $ROLE" "b $AST"; do
set -- $pair
mutant "m12$1-loader-drops-field" "$2" "$R" 'messaging: document.messaging === undefined ? null : checkMessaging(document.messaging),' 'messaging: null,'
done
# m13: the shipped assistant file lets it send to the PM, or hear from the coder
for pair in "a $MSG" "b $AST"; do
set -- $pair
mutant "m13$1-assistant-sendTo-pm" "$2" "$A" '"sendTo": ["human", "chief-of-staff"]' '"sendTo": ["human", "chief-of-staff", "pm"]'
done
for pair in "a $MSG" "b $AST"; do
set -- $pair
mutant "m14$1-assistant-receiveFrom-coder" "$2" "$A" '"receiveFrom": ["human", "chief-of-staff"]' '"receiveFrom": ["human", "chief-of-staff", "coder"]'
done
# m15: the shipped assistant file has no messaging field at all
for pair in "a $MSG" "b $AST"; do
set -- $pair
mutant "m15$1-assistant-unlimited" "$2" "$A" '"credentials": [],
"messaging": {
"sendTo": ["human", "chief-of-staff"],
"receiveFrom": ["human", "chief-of-staff"]
}
}' '"credentials": []
}'
done
# m16: review.request skips the lists (round 1 F1)
mutant m16-review-request-unchecked "$MSG" "$B" "this.#checkMessaging(s, a.to);
const mid = randomUUID();
// Row 105" "const mid = randomUUID();
// Row 105"
# m17: review.verdict skips the lists (round 1 F1)
mutant m17-review-verdict-unchecked "$MSG" "$B" "this.#checkMessaging(s, r.from);" "void 0;"
echo done