#!/usr/bin/env bash # Row 141 round 1 (#1635, sage-0573, sage-0589): messages addressed by agent name. Every test file # the candidate touches, each singly; then one kill per new check. A kill edits one source file in # place, runs one test file, which must fail on the named test (and on exactly N tests when N is # given), and restores the file; the script checks every restored file's sha256 against the one it # started with. Last, the whole bus, business, cli and harness suites. Not the full gate: every # scripts/test-*.sh is the landing gate's. # Usage: round.sh set -uo pipefail WT=$(cd "$1" && pwd -P); LOGS=$(mkdir -p "$2" && cd "$2" && pwd -P) INHERITED_TMPDIR=/mnt/storage/scratch/tmp export TMPDIR="$HOME/darkwing-scratch/r141/tmp"; mkdir -p "$TMPDIR" SRCS=(roles/chief-of-staff.json packages/bus/src/broker.mjs packages/business/src/vocabulary.mjs) declare -A SUM for f in "${SRCS[@]}"; do SUM[$f]=$(sha256sum "$WT/$f" | cut -d' ' -f1); done restore() { local f bad=0 for f in "${SRCS[@]}"; do if [ -f "$WT/$f.orig" ]; then mv -f "$WT/$f.orig" "$WT/$f"; fi [ "$(sha256sum "$WT/$f" | cut -d' ' -f1)" = "${SUM[$f]}" ] || { echo "RESTORE_MISMATCH $f"; bad=1; } done return $bad } trap restore EXIT echo "base $(git -C "$WT" rev-parse --short HEAD) start $(date -u +%H:%M:%S)" # run name package-dir test-file [tmpdir] run() { local log="$LOGS/$1.log" start; start=$(date -u +%H:%M:%S) (cd "$WT/$2" && TMPDIR="${4:-$TMPDIR}" timeout 600 node --test "tests/$3") > "$log" 2>&1 local rc=$? echo "$1 rc=$rc $start-$(date -u +%H:%M:%S) $(grep -E 'ℹ (pass|fail|skipped) ' "$log" | tr '\n' ' ')" return $rc } # probe name source sed-expression changed-line package-dir test-file fails test-name # fails is the exact number of failing tests, or "any" for one or more. probe() { local src="$WT/$2" cp -p "$src" "$src.orig" sed -i "$3" "$src" if [ "$(grep -cF -- "$4" "$src")" != 1 ]; then echo "STOP $1: the edit didn't apply"; exit 1; fi echo "$1 edit: $2 $(grep -nF -- "$4" "$src" | sed 's/^\([0-9]*\):\s*/line \1: /')" run "$1" "$5" "$6" local rc=$? log="$LOGS/$1.log" n restore || exit 1 n=$(grep -E '^ℹ fail [0-9]+$' "$log" | awk '{print $3}') if [ $rc -ne 0 ] && grep -qF "✖ $8" "$log" && { [ "$7" = any ] && [ "${n:-0}" -ge 1 ] || [ "${n:-}" = "$7" ]; }; then echo "$1 KILLED: \"$8\" failed, $n failing" else echo "STOP $1: expected \"$8\" to fail ($7 failing), got ${n:-none}"; exit 1 fi } for t in names name-migration spec-migration comment-migration thread-migration schema project-runtime chief-of-staff hard-waits messaging broker; do run "bus-$t" packages/bus "$t.test.mjs" || { echo "STOP bus-$t"; exit 1; } done for t in chief-of-staff role hard-waits; do run "business-$t" packages/business "$t.test.mjs" || { echo "STOP business-$t"; exit 1; } done for t in names app verbs follow tester; do run "cli-$t" packages/cli "$t.test.mjs" || { echo "STOP cli-$t"; exit 1; } done for t in tools bundle; do run "harness-$t" packages/harness "$t.test.mjs" || { echo "STOP harness-$t"; exit 1; } done CHIEF="REQ-MSG-2: the chief of staff names an instance under its within-role grant, and the write is logged" REFUSED="REQ-MSG-2: a role without the grant, or holding it cross-role, is refused not-authorized" UNIQUE="REQ-MSG-2: a name is unique in the business and is never an instance key or a role name" SEND="REQ-MSG-2: a send by name reaches the instance and records both; a send by instance key is unchanged" KEY="REQ-MSG-2: an instance key beats a name that equals it, as when a later instance takes the key" ROLE="REQ-MSG-2: a role name beats a name that equals it, as when a later definition takes the word (sage-0590)" ROUTED="REQ-WAIT-3: instance.name is routed under REQ-MSG-2 (1.5), not a hard wait (row 141, sage-0589)" # sage-0589: without the grant in the shipped role file, the chief's write is refused. probe kill-1-grant roles/chief-of-staff.json \ 's/"record.write", "instance.name"\]/"record.write"]/' \ '"organization.change", "record.write"],' packages/bus names.test.mjs any "$CHIEF" # Without name resolution, a send by name is an unknown role. probe kill-2-resolution packages/bus/src/broker.mjs \ "s/? this.#named(s.business, a.to) : null;/? null : null;/" \ "? null : null;" packages/bus names.test.mjs 1 "$SEND" # Without the key-first check, a name equal to a key takes the key's messages. probe kill-2b-key-first packages/bus/src/broker.mjs \ "s/const named = typeof a.to === 'string' \&\& !Object.hasOwn(b.roles, a.to) \&\& a.to !== 'human' \&\& a.to !== 'tester'$/const named = typeof a.to === 'string' \&\& true/" \ "const named = typeof a.to === 'string' && true" packages/bus names.test.mjs 1 "$KEY" # sage-0590: without the role check at send time, a name equal to a later definition takes its sends. probe kill-2c-role-first packages/bus/src/broker.mjs \ "s/\&\& !this.#definitions(b).has(a.to)$/\&\& true \/\* killed \*\//" \ "&& true /* killed */" packages/bus names.test.mjs 1 "$ROLE" probe kill-3a-name-is-instance packages/bus/src/broker.mjs \ "s/if (Object.hasOwn(b.roles, a.name) || a.name === 'human' || a.name === 'tester') fail('name-is-instance');/if (false) fail('name-is-instance');/" \ "if (false) fail('name-is-instance');" packages/bus names.test.mjs 1 "$UNIQUE" probe kill-3b-name-is-role packages/bus/src/broker.mjs \ "s/if (this.#definitions(b).has(a.name)) fail('name-is-role');/if (false) fail('name-is-role');/" \ "if (false) fail('name-is-role');" packages/bus names.test.mjs 1 "$UNIQUE" # Without the refusal, a role holding instance.name cross-role reaches #consumeAuthority. probe kill-4-not-authorized packages/bus/src/broker.mjs \ "s/if (!s.human && this.#classification(s, 'instance.name', a.instance) !== 'within-role') {/if (false) {/" \ "if (false) {" packages/bus names.test.mjs any "$REFUSED" # A launched session's human is false, not null: with ?? the chief's row records by=false (stored 0). probe kill-6-by packages/bus/src/broker.mjs \ "s/s.human || s.run,/s.human ?? s.run,/" \ "s.human ?? s.run," packages/bus names.test.mjs 1 "$CHIEF" # Line 77 is ROUTED's entry; line 37 is ACTIONS'. probe kill-5-routed packages/business/src/vocabulary.mjs \ '77s/^ "instance.name",$/ \/\/ killed: "instance.name",/' \ '// killed: "instance.name",' packages/business hard-waits.test.mjs any "$ROUTED" # The whole suites once more, on the restored candidate. The cli launcher and thread-launch tests # take the inherited TMPDIR (the bwrap runner fails under an override), so they run apart. for pkg in bus business harness; do start=$(date -u +%H:%M:%S) (cd "$WT/packages/$pkg" && timeout 900 node --test tests/*.test.mjs) > "$LOGS/$pkg.log" 2>&1 rc=$? echo "$pkg rc=$rc $start-$(date -u +%H:%M:%S) $(grep -E 'ℹ (pass|fail|skipped) ' "$LOGS/$pkg.log" | tr '\n' ' ')" [ $rc -eq 0 ] || { echo "STOP $pkg"; exit 1; } done start=$(date -u +%H:%M:%S) (cd "$WT/packages/cli" && timeout 900 node --test $(ls tests/*.test.mjs | grep -v -e launcher.test -e thread-launch.test)) > "$LOGS/cli.log" 2>&1 rc=$? echo "cli rc=$rc $start-$(date -u +%H:%M:%S) $(grep -E 'ℹ (pass|fail|skipped) ' "$LOGS/cli.log" | tr '\n' ' ')" [ $rc -eq 0 ] || { echo "STOP cli"; exit 1; } run cli-launcher packages/cli launcher.test.mjs "$INHERITED_TMPDIR" || { echo "STOP cli-launcher"; exit 1; } run cli-thread-launch packages/cli thread-launch.test.mjs "$INHERITED_TMPDIR" || { echo "STOP cli-thread-launch"; exit 1; } echo "ROUND_PASSED $(date -u +%H:%M:%S)"