#!/usr/bin/env bash # 165-visibility-edge-cases — the edge cases of agent visibility (tags, the # per-agent messaging policy, delegated grants, analytics) that 160 does not # walk, driven through the raw /api/v1 wire contract: # # defaults fresh agents carry tags [] / messaging any, and DMs, room # posts and human DMs behave exactly as before; # validation tag slug shape, the 10-tag cap, sorted tags, bad policy # values, bad grant scopes and principal ids → 400; # open-dm-policy a policy change bites an ALREADY-OPEN agent↔agent DM: sends # (and re-ensures) refused after tightening with the blocking # side named, history still readable, allowed after loosening; # both sides blocking → both named; # tag-removed `tags` policy + the shared tag removed from ONE side # mid-conversation → refused both ways; re-adding it reopens; # agent-to-human `tags` / `none` never restrict agent→human DMs or room posts; # delegate a `tag:T` holder: cannot grant (itself, its granter, anyone), # cannot change its own tags/policy, cannot act outside T, # CAN add T to an untagged agent (spec: "add/remove that tag # on agents"), loses everything once its grant is revoked; # chief a `tags:*` holder grants only `tag:`, never to itself, # and a `tag:T` holder cannot touch it (outranked) or revoke a # grant it did not create; # no-authority a plain member / a grant-less agent is refused tags, # messaging, grants and another agent's analytics; # cross-org another org's agent / grant / member ids → 404 (never 403), # byte-identical to a fabricated id; # analytics a refused send/ensure counts nothing; an agent→human DM # lands in withHumans; # cascade deleting an agent drops the grants it held; removing a # human from the org drops the grants they held. # # Every case runs to completion and the scenario prints a per-case PASS/FAIL # table; a failing case prints the exact request and response. Behaviour the # spec does not define is printed as OBSERVED (informational, never a failure). set -euo pipefail SCENARIO_NAME="165-visibility-edge-cases" . "$(cd "$(dirname "$0")/.." && pwd)/lib.sh" scenario_start # -------------------------------------------------------------------------- # Harness: actors by name, one request helper, per-case isolation. # -------------------------------------------------------------------------- declare -A TOK=() # actor name → bearer token (ses_ / agk_) declare -A ID=() # actor name → principal id (usr_ / agt_) # req [json-body] — sets ST (status), BODY, LAST (the # exchange, verbatim, for failure reports). Never fails by itself. req() { local who="$1" m="$2" p="$3" b="${4:-}" f f="$SPARROW_TMPROOT/resp.$BASHPID" local -a args=(-sS -o "$f" -w '%{http_code}' -X "$m" "$SERVER/api/v1$p" -H "authorization: Bearer ${TOK[$who]}") [ -n "$b" ] && args+=(-H 'content-type: application/json' -d "$b") ST="$(curl "${args[@]}" 2>/dev/null || true)" BODY="$(cat "$f" 2>/dev/null || true)" LAST="[$who] $m $p${b:+ $b} → $ST $BODY" } # expect_status