121 lines
4.5 KiB
Bash
121 lines
4.5 KiB
Bash
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
# Plan-18 MVP-2 Track 4: agent/skill selection is governed by registry + RBAC.
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "$SCRIPT_DIR/../scripts/bash/casan-paths.sh"
|
|
RESOLVER="$CASAN_HARNESS_ROOT/scripts/bash/chat-agent-resolver.py"
|
|
TURN="$CASAN_HARNESS_ROOT/scripts/bash/chat-turn.py"
|
|
WORK="$(mktemp -d)"
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
export CASAN_STATE_ROOT="$WORK/state"
|
|
|
|
PASS=0; FAIL=0
|
|
pass() { echo "PASS: $1"; PASS=$((PASS + 1)); }
|
|
fail() { echo "FAIL: $1"; FAIL=$((FAIL + 1)); }
|
|
|
|
echo "===== Plan-18 MVP-2 agent/skill selection ====="
|
|
|
|
python3 "$RESOLVER" list-agents --role viewer > "$WORK/list.json"
|
|
python3 - "$WORK/list.json" <<'PY' \
|
|
&& pass "agent registry lists versioned agents with role visibility" || fail "agent registry listing invalid"
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
ids = {a["id"]: a for a in d["agents"]}
|
|
assert d["success"] is True
|
|
assert {"evidence-reader", "ops-operator", "codegen-draft"}.issubset(ids)
|
|
assert ids["evidence-reader"]["allowed_for_role"] is True
|
|
assert ids["ops-operator"]["allowed_for_role"] is False
|
|
PY
|
|
|
|
python3 "$RESOLVER" bind --agent evidence-reader --skill evidence-summary --role viewer --actor alice --project default --tenant tenant-a > "$WORK/viewer.json"
|
|
python3 - "$WORK/viewer.json" <<'PY' \
|
|
&& pass "viewer can bind read-only evidence-reader" || fail "viewer could not bind read-only agent"
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
assert d["success"] is True
|
|
assert d["decision"] == "BOUND"
|
|
assert d["agent_selected"] == "evidence-reader"
|
|
assert d["delegation_level"] == 0
|
|
assert d["tool_allowlist"] == []
|
|
PY
|
|
|
|
set +e
|
|
python3 "$RESOLVER" bind --agent ops-operator --role viewer --actor alice --project default --tenant tenant-a > "$WORK/viewer-op.json"
|
|
RC=$?
|
|
set -e 2>/dev/null || true
|
|
python3 - "$WORK/viewer-op.json" "$RC" <<'PY' \
|
|
&& pass "viewer cannot select operator agent" || fail "viewer selected operator agent"
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
assert int(sys.argv[2]) == 2
|
|
assert d["success"] is False
|
|
assert d["decision"] == "DENIED"
|
|
assert d["reason"] == "role_not_allowed_for_agent"
|
|
PY
|
|
|
|
set +e
|
|
python3 "$RESOLVER" bind --agent evidence-reader --role viewer --actor alice --project default --tenant tenant-a --target-tenant tenant-b > "$WORK/cross-tenant.json"
|
|
RC=$?
|
|
set -e 2>/dev/null || true
|
|
python3 - "$WORK/cross-tenant.json" "$RC" <<'PY' \
|
|
&& pass "cross-tenant agent binding is denied by RBAC" || fail "cross-tenant agent binding was allowed"
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
assert int(sys.argv[2]) == 2
|
|
assert d["success"] is False
|
|
assert d["decision"] == "DENIED"
|
|
assert "rbac_denied" in d["reason"]
|
|
PY
|
|
|
|
set +e
|
|
python3 "$RESOLVER" bind --agent evidence-reader --role viewer --actor alice --project default --tenant tenant-a --delegation-level 1 > "$WORK/delegation.json"
|
|
RC=$?
|
|
set -e 2>/dev/null || true
|
|
python3 - "$WORK/delegation.json" "$RC" <<'PY' \
|
|
&& pass "delegation above agent max requires approval" || fail "delegation escalation was not held"
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
assert int(sys.argv[2]) == 3
|
|
assert d["success"] is False
|
|
assert d["decision"] == "REQUIRES_APPROVAL"
|
|
assert d["requires_approval"] is True
|
|
PY
|
|
|
|
set +e
|
|
python3 "$RESOLVER" bind --agent ops-operator --role operator --actor bob --project default --tenant tenant-a --tool shell > "$WORK/tool.json"
|
|
RC=$?
|
|
set -e 2>/dev/null || true
|
|
python3 - "$WORK/tool.json" "$RC" <<'PY' \
|
|
&& pass "tool outside agent allowlist is blocked" || fail "tool outside allowlist was accepted"
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
assert int(sys.argv[2]) == 2
|
|
assert d["success"] is False
|
|
assert d["decision"] == "DENIED"
|
|
assert d["action_gate"]["outcome"] == "BLOCK"
|
|
PY
|
|
|
|
set +e
|
|
python3 "$TURN" ask --message "run tests" --agent ops-operator --role viewer --actor alice --chat-id agent-chat --tenant tenant-a > "$WORK/turn-deny.json"
|
|
RC=$?
|
|
set -e 2>/dev/null || true
|
|
python3 - "$WORK/turn-deny.json" "$RC" <<'PY' \
|
|
&& pass "chat-turn denies unauthorized selected agent before operator runtime" || fail "chat-turn bypassed agent resolver"
|
|
import json, sys
|
|
d = json.load(open(sys.argv[1]))
|
|
assert int(sys.argv[2]) == 2
|
|
assert d["success"] is False
|
|
assert d["decision"] == "DENIED"
|
|
assert d["agent_selected"] == "ops-operator"
|
|
PY
|
|
|
|
python3 "$RESOLVER" verify-audit > "$WORK/audit.out" \
|
|
&& grep -q "CHAT_AGENT_AUDIT ok=true" "$WORK/audit.out" \
|
|
&& pass "agent binding audit hash chain verifies" || fail "agent binding audit invalid"
|
|
|
|
echo ""
|
|
echo "===== CHAT AGENT SUMMARY: PASS=$PASS FAIL=$FAIL ====="
|
|
[[ "$FAIL" -eq 0 ]] || exit 1
|