Files
jason.woltjeandClaude Sonnet 5.5 8a73f713cf agents: Researcher row 132 packet (#1628, end_run and change_organization harness tools)
Round 1 packet in agents/researcher/work/queue-132/: row132.patch (8 files, +162 -19) on b6a588cd2,
candidate manifest, build.md, 28 mutants killed, full gate green. No candidate change applied here.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-15 13:05:03 -05:00

103 lines
6.5 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 132 (#1628): harness tools for ending a run and changing the organization file.
# 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> (MUTANT_DRY=1 only proves every edit matches exactly once)
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
if [ -n "${MUTANT_DRY:-}" ]; then echo "$1: edit ok (dry)"; return; fi
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"
}
H=packages/harness; TT=$H/tests/tools.test.mjs; BT=$H/tests/bundle.test.mjs; RT=$H/tests/runner.test.mjs
T=$H/src/tools.mjs; BD=$H/src/bundle.mjs; RN=$H/src/runner.mjs; B=packages/bus/src/broker.mjs
for t in "$TT" "$BT" "$RT"; do run "control-$(basename "$t" .test.mjs)" "$t"; done
# The chief gets both tools; a role without the grants gets neither
# m1: end_run is offered to every role
mutant m1-end-run-always "$TT" "$T" 'if (held.has("role.end")) {' 'if (true) {'
# m2: change_organization is offered to every role
mutant m2-change-organization-always "$TT" "$T" 'if (held.has("organization.change")) {' 'if (true) {'
# m3: the chief gets no end_run
mutant m3-end-run-never "$TT" "$T" 'if (held.has("role.end")) {' 'if (false) {'
mutant m3b-end-run-never-bundle "$BT" "$T" 'if (held.has("role.end")) {' 'if (false) {'
# m4: the chief gets no change_organization
mutant m4-change-organization-never "$TT" "$T" 'if (held.has("organization.change")) {' 'if (false) {'
mutant m4b-change-organization-never-bundle "$BT" "$T" 'if (held.has("organization.change")) {' 'if (false) {'
# What each tool sends
# m5: end_run goes to the wrong verb
mutant m5-end-run-verb "$TT" "$T" 'add("end_run", "role.revoke", "role.end", ' 'add("end_run", "role.end", "role.end", '
mutant m5b-end-run-verb-runner "$RT" "$T" 'add("end_run", "role.revoke", "role.end", ' 'add("end_run", "role.end", "role.end", '
# m6: end_run has no action, so the manifest lists role.end as untyped
mutant m6-end-run-action "$TT" "$T" 'add("end_run", "role.revoke", "role.end", ' 'add("end_run", "role.revoke", null, '
# m7: change_organization goes to the human's verb
mutant m7-change-organization-verb "$TT" "$T" 'add("change_organization", "organization.set", "organization.change",' 'add("change_organization", "limits.set", "organization.change",'
mutant m7b-change-organization-verb-runner "$RT" "$T" 'add("change_organization", "organization.set", "organization.change",' 'add("change_organization", "limits.set", "organization.change",'
# m8: change_organization has no action, so the manifest lists organization.change as untyped
mutant m8-change-organization-action "$TT" "$T" 'add("change_organization", "organization.set", "organization.change",' 'add("change_organization", "organization.set", null,'
# m9: reason isn't required
mutant m9-reason-optional "$TT" "$T" '}, ["role", "reason"]));' '}, ["role"]));'
# m10: end_run takes a decision
mutant m10-end-run-decision "$TT" "$T" ' }, ["role", "reason"]));' ' decision: COMMON.decision,
}, ["role", "reason"]));'
# m11: the reason has no useful bound
mutant m11-reason-unbounded "$TT" "$T" 'maxLength: 500 }' 'maxLength: 50000 }'
# m12: revision isn't required
mutant m12-revision-optional "$TT" "$T" '}, ["revision"]));' '}, []));'
# m13: revision takes any string
mutant m13-revision-pattern "$TT" "$T" 'pattern: "^[a-f0-9]{64}$" }' 'pattern: "^.*$" }'
# m14: the description drops the own-run and assistant limits
mutant m14-description-limits "$TT" "$T" "You can't end your own run or the assistant's; the broker refuses with role-end-refused." 'The broker refuses some runs.'
# One key list
# m15: the harness list drops a key
mutant m15-harness-key-dropped "$TT" "$T" '"revision", "limits", "models", "instances"])' '"revision", "limits", "instances"])'
# m16: the broker list gains a key
mutant m16-broker-key-added "$TT" "$B" "export const ORGANIZATION_KEYS = ['revision', 'limits', 'models', 'instances'];" "export const ORGANIZATION_KEYS = ['revision', 'limits', 'models', 'instances', 'plugins'];"
# The bundle text
# m17: a role.end holder keeps the human-only text
mutant m17-bundle-text-unchanged "$BT" "$BD" 'lines.push(named.has("end_run")' 'lines.push(false'
# m18: every role gets the role.end text
mutant m18-bundle-text-for-all "$BT" "$BD" 'lines.push(named.has("end_run")' 'lines.push(true'
# m19: the text drops the reason requirement
mutant m19-text-reason "$BT" "$BD" '; the reason is required and goes into the log' ''
# m20: the text drops the own-run and assistant limits
mutant m20-text-limits "$BT" "$BD" " You can't end your own run or the assistant's." ''
# m21: the text drops the decision fallback
mutant m21-text-fallback "$BT" "$BD" 'For anything the broker refuses, raise a role.revoke decision' 'For anything else, raise a role.revoke decision'
# Through the runner
# m22: the runner drops the tool's arguments
mutant m22-runner-drops-args "$RT" "$RN" 'await client.call(tool.verb, args)' 'await client.call(tool.verb, {})'
# m23: the runner sends the tool's name as the verb
mutant m23-runner-name-as-verb "$RT" "$RN" 'await client.call(tool.verb, args)' 'await client.call(tool.name, args)'
# m24: the runner hides the broker's refusal code
mutant m24-runner-hides-code "$RT" "$RN" 'error: /^[a-z-]{1,64}$/.test(e.code ?? "") ? e.code : "tool-failed"' 'error: "tool-failed"'
echo done