Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
120 lines
5.8 KiB
Bash
Executable File
120 lines
5.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Dewey's row 149 round 4 mutants (sage-0707, sage-0709; round 3's sage-0684, sage-0691): one per
|
|
# contract-line assertion in packages/business/tests/workflow-roles.test.mjs. m1-m16 are round 3's,
|
|
# with m6, m7, m8 and m11 re-anchored to round 4's wraps; m17-m25 are round 4's. Each mutant edits one role file once; the test
|
|
# file must then fail. Usage: mutants.sh <r149-r2 worktree> [--dry-run]
|
|
# --dry-run applies and restores each edit (exactly one match, file hash back) and runs no test.
|
|
set -uo pipefail
|
|
WT=$(cd "$1" && pwd) || exit 2
|
|
DRY=${2:-}
|
|
OUT=$(cd "$(dirname "$0")" && pwd) || exit 2
|
|
mkdir -p "$OUT/mutants" "$OUT/backup" || exit 2
|
|
export TMPDIR=$HOME/dewey-scratch/tmp
|
|
TEST=packages/business/tests/workflow-roles.test.mjs
|
|
(cd "$WT" && sha256sum -c --quiet "$OUT/../candidate-manifest.sha256") || { echo "manifest check failed"; exit 2; }
|
|
|
|
# id|file|old|new (old must occur exactly once; new may be empty)
|
|
MUTANTS=$(cat <<'EOF'
|
|
m1|roles/merge-gate.md|The run that raises the decision ends its
|
|
turn there. |
|
|
m2|roles/merge-gate.md|don't try to land and don't raise again.|don't try to land; raise it again.
|
|
m3|roles/merge-gate.md|A run launched after the approval has `land_work`|The raising run has `land_work`
|
|
m4|roles/merge-gate.md|- If the decision is declined,|- Once the decision is resolved with its approval option, call
|
|
`land_work`.
|
|
- If the decision is declined,
|
|
m5|roles/cto.md|send the chief of staff one message|send the pm one message
|
|
m6|roles/cto.md|the decision id
|
|
and the task reference|the task reference
|
|
m7|roles/chief-of-staff.md|launch
|
|
that instance with `launch`.|tell the pm.
|
|
m8|roles/chief-of-staff.md|Then end it with `end_run`, with a reason naming the
|
|
decision, and launch once it has exited.|Then wait for it to end.
|
|
m9|roles/pm.md|A `role.revoke` raise whose target is the assistant is a hard wait too:
|
|
it goes to the human, cited or not.
|
|
|
|
|
m10|roles/chief-of-staff.md|A `role.revoke` raise whose target is the assistant is a hard wait too:
|
|
it goes to the human, cited or not.
|
|
|
|
|
m11|roles/chief-of-staff.md|`launch` for that
|
|
instance was refused `instance-running`, |
|
|
m12|roles/chief-of-staff.md| End no other run.|
|
|
m13|roles/chief-of-staff.md|with `mosaic stop`, apart from the merge
|
|
relaunch under Duties.|with `mosaic stop`.
|
|
m14|roles/merge-gate.md|then pass `project`, `spec_path` `spec/north-star.md` and
|
|
`spec_commit`.|then pass `project`.
|
|
m15|roles/merge-gate.md|If the raise is refused `stale-spec`, read the spec
|
|
again and raise once more with the current commit. |
|
|
m16|roles/merge-gate.md|raise uncited and say so in the question.|don't raise.
|
|
m17|roles/cto.md|, the decision id
|
|
and the task reference (the decision's `authorization.target`).| and the decision id.
|
|
m18|roles/chief-of-staff.md|the decision id and the task reference, launch|and the decision id, launch
|
|
m19|roles/chief-of-staff.md|send the instance one message with `send_message` naming the task
|
|
reference and the decision id.|wait for its turn.
|
|
m20|roles/chief-of-staff.md| A run takes a turn only on a message it
|
|
receives, and the approval went to the run that raised it, so the new
|
|
run lands on your message.|
|
|
m21|roles/chief-of-staff.md|, the first time or after an
|
|
end,|
|
|
m22|roles/merge-gate.md|Its turn comes on the chief of staff's
|
|
message naming the task reference and the decision id: call
|
|
`land_work` with them.|Call `land_work` with the task reference
|
|
and the decision id.
|
|
m23|roles/merge-gate.md|in the `land_work` reply or in the
|
|
later RESULT|in the `land_work` reply
|
|
m24|roles/merge-gate.md|Report the task landed in your answer,
|
|
and don't land or raise again.|Raise the decision again.
|
|
m25|roles/merge-gate.md|means the landing already happened: another landing of
|
|
the decision got there first.|means the decision is stale: raise it again.
|
|
EOF
|
|
)
|
|
|
|
status=0
|
|
echo "start $(date -u +%FT%TZ) head $(git -C "$WT" rev-parse --short HEAD) mode=${DRY:-run}"
|
|
if [ -z "$DRY" ]; then
|
|
(cd "$WT" && setsid -w timeout -k 10 300 node --test "$TEST") > "$OUT/mutants/control.log" 2>&1
|
|
rc=$?; echo "control rc=$rc"; [ $rc -eq 0 ] || { echo "control must pass"; exit 1; }
|
|
fi
|
|
# Records are split on a line holding only the next id: python does the parsing and the edit.
|
|
ids=$(python3 - "$MUTANTS" <<'PY'
|
|
import re, sys
|
|
for m in re.finditer(r'^(m\d+)\|', sys.argv[1], re.M): print(m.group(1))
|
|
PY
|
|
)
|
|
for id in $ids; do
|
|
file=$(python3 - "$MUTANTS" "$id" <<'PY'
|
|
import re, sys
|
|
recs = re.split(r'^(?=m\d+\|)', sys.argv[1], flags=re.M)
|
|
r = next(x for x in recs if x.startswith(sys.argv[2] + '|'))
|
|
print(r.split('|', 3)[1])
|
|
PY
|
|
) || { echo "$id: parse failed"; status=1; continue; }
|
|
cp "$WT/$file" "$OUT/backup/$id" || exit 1
|
|
before=$(sha256sum < "$WT/$file")
|
|
python3 - "$MUTANTS" "$id" "$WT/$file" <<'PY' || { echo "$id: edit refused"; status=1; continue; }
|
|
import re, sys, pathlib
|
|
recs = re.split(r'^(?=m\d+\|)', sys.argv[1], flags=re.M)
|
|
r = next(x for x in recs if x.startswith(sys.argv[2] + '|'))
|
|
_, _, old, new = r.split('|', 3)
|
|
new = new[:-1] if new.endswith('\n') else new # the record's own line end
|
|
p = pathlib.Path(sys.argv[3]); t = p.read_text()
|
|
n = t.count(old)
|
|
if n != 1: sys.exit(f"{sys.argv[2]}: {n} matches")
|
|
p.write_text(t.replace(old, new))
|
|
PY
|
|
if [ -n "$DRY" ]; then
|
|
echo "$id $file applies once"
|
|
else
|
|
(cd "$WT" && setsid -w timeout -k 10 300 node --test "$TEST") > "$OUT/mutants/$id.log" 2>&1; rc=$?
|
|
if [ $rc -ne 0 ]; then
|
|
echo "$id $file caught: $(grep -m1 -E '^not ok|✖' "$OUT/mutants/$id.log" | cut -c1-140)"
|
|
else
|
|
echo "$id $file SURVIVED"; status=1
|
|
fi
|
|
fi
|
|
cp "$OUT/backup/$id" "$WT/$file" || exit 1
|
|
[ "$(sha256sum < "$WT/$file")" = "$before" ] || { echo "$id: restore failed"; exit 1; }
|
|
done
|
|
(cd "$WT" && sha256sum -c --quiet "$OUT/../candidate-manifest.sha256") || { echo "manifest check failed after"; status=1; }
|
|
echo "end $(date -u +%FT%TZ) status=$status"
|
|
exit $status
|