#!/usr/bin/env bash # Sandboxed selftests for the conductor auto-apply policy gate. # # Builds a throwaway target repo + worker workspace + fake run records, then # exercises every gate: policy validation, allowlist, syntax gates, suite # failure revert, disabled policy, missing/failed runs. No real model calls. set -uo pipefail cd "$(dirname "$0")/.." SANDBOX="$(mktemp -d)" trap 'rm -rf "$SANDBOX"' EXIT PASS=0 FAIL=0 # Status colors: terminal-only, NO_COLOR-respecting; plain when piped. if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then C_OK=$'\033[0;32m'; C_FAIL=$'\033[0;31m'; C_RESET=$'\033[0m' else C_OK=""; C_FAIL=""; C_RESET="" fi check_rc() { # name expectedRc command... local name="$1" expected="$2" shift 2 local rc "$@" >/dev/null 2>&1 rc=$? if [ "$rc" -eq "$expected" ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $name (exit $rc)" else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $name (exit $rc, expected $expected)" fi } check() { if [ "$2" = "0" ]; then PASS=$((PASS+1)); echo "${C_OK}OK${C_RESET} $1"; else FAIL=$((FAIL+1)); echo "${C_FAIL}FAIL${C_RESET} $1"; fi } # ---- infrastructure: target repo + worker workspace + fake run ---- git clone -q . "$SANDBOX/repo" # The clone carries committed state only - give the target its policy and # commit it so the tree starts clean (untracked policy would fail target_clean). cp conductor-policy.json "$SANDBOX/repo/conductor-policy.json" git -C "$SANDBOX/repo" add conductor-policy.json git -C "$SANDBOX/repo" -c user.name=suite -c user.email=suite@local commit -q -m policy mkdir -p "$SANDBOX/data/workspaces" "$SANDBOX/data/runs" git clone -q "$SANDBOX/repo" "$SANDBOX/data/workspaces/stack-repo" TARGET="$SANDBOX/repo" WS="$SANDBOX/data/workspaces/stack-repo" cat > "$SANDBOX/config.json" < "$SANDBOX/data/runs/$RUN_OK/result.json" ws_edit() { printf '\n%s\n' "$2" >> "$WS/$1"; } ws_reset() { git -C "$WS" checkout -q -- . 2>/dev/null; git -C "$WS" clean -qfd; } target_clean() { [ -z "$(git -C "$TARGET" status --porcelain)" ]; } set_policy() { # enabled suites (commits: the target tree must stay clean) local suites="[\"$2\"]" printf '{"policyVersion":1,"autoApply":{"enabled":%s,"allowedPaths":["scripts/**","docs/**","README.md"],"suites":%s}}' "$1" "$suites" \ > "$TARGET/conductor-policy.json" git -C "$TARGET" add conductor-policy.json git -C "$TARGET" -c user.name=suite -c user.email=suite@local commit -q -m "policy update" } set_policy true "test-config" # T1: dry run - allowed change, nothing applied ws_edit "README.md" "worker dry-run line" check_rc "dry-run: allowed change, exit 0, nothing committed" 0 \ scripts/conductor-apply.sh "$RUN_OK" --dry-run if git -C "$TARGET" log --format=%s | grep -q "auto-applied"; then check "dry-run committed nothing" 1 else check "dry-run committed nothing" 0 fi ws_reset # T2: apply - allowed change, suites pass, commit created ws_edit "README.md" "worker applied line" check_rc "apply: allowed change exits 0" 0 scripts/conductor-apply.sh "$RUN_OK" git -C "$TARGET" log -1 --format=%s | grep -q "auto-applied patch from run $RUN_OK" \ && check "apply: attribution in commit subject" 0 || check "apply: attribution in commit subject" 1 target_clean() { [ -z "$(git -C "$TARGET" status --porcelain)" ]; } target_clean && check "apply: target tree clean after commit" 0 || check "apply: target tree clean after commit" 1 git -C "$TARGET" reset -q --hard HEAD~1 # T3: disallowed path refused ws_edit "Containerfile" "# worker touch" check_rc "disallowed path refused" 1 scripts/conductor-apply.sh "$RUN_OK" target_clean && check "disallowed path: target untouched" 0 || check "disallowed path: target untouched" 1 ws_reset # T4: syntax gate - broken .mjs on an allowed path printf 'this is not (valid js\n' > "$WS/scripts/broken-worker.mjs" check_rc "syntax gate refused broken .mjs" 1 scripts/conductor-apply.sh "$RUN_OK" target_clean && check "syntax gate: target untouched" 0 || check "syntax gate: target untouched" 1 ws_reset # T5: suite failure - allowed change breaks a policy suite -> auto-revert printf '\nexit 7\n' >> "$TARGET/scripts/test-config.sh" ws_edit "README.md" "worker change that will fail suites" check_rc "suite failure refused" 1 scripts/conductor-apply.sh "$RUN_OK" git -C "$TARGET" checkout -q -- scripts/test-config.sh target_clean && check "suite failure: target reverted to clean" 0 || check "suite failure: target reverted to clean" 1 # T6: disabled policy set_policy false "test-config" ws_edit "README.md" "worker line while disabled" check_rc "disabled policy refused" 2 scripts/conductor-apply.sh "$RUN_OK" target_clean && check "disabled policy: target untouched" 0 || check "disabled policy: target untouched" 1 ws_reset set_policy true "test-config" # T7: failed run refused RUN_FAIL="r-20260903T000000000Z-fail00001" mkdir -p "$SANDBOX/data/runs/$RUN_FAIL" printf '{"runVersion":1,"runId":"%s","taskId":"t","status":"failed","workspace":"stack-repo"}' "$RUN_FAIL" \ > "$SANDBOX/data/runs/$RUN_FAIL/result.json" ws_edit "README.md" "worker line from failed run" check_rc "failed run refused" 1 scripts/conductor-apply.sh "$RUN_FAIL" target_clean && check "failed run: target untouched" 0 || check "failed run: target untouched" 1 ws_reset # T8/T9: missing run + invalid policy check_rc "missing run exits 4" 4 scripts/conductor-apply.sh r-missing printf '{"policyVersion":9}' > "$TARGET/conductor-policy.json" check_rc "invalid policy exits 2" 2 scripts/conductor-apply.sh "$RUN_OK" git -C "$TARGET" checkout -q -- conductor-policy.json echo echo "selftest: $PASS passed, $FAIL failed" [ "$FAIL" -eq 0 ]