Warden plan

08 - workspace reads on the ability engine (D36)

← eval suite index


tier: needs-postgres requires: []


08 - workspace reads on the ability engine (D36)

What it proves

The workspaces router READS run on the ability engine and are provably equivalent to the retired hand-written visibility predicate: a read Workspace rule exists for non-admin roles (open arm + membership arm), visibility is mapped in SUBJECT_TABLES.Workspace so the open arm is a real predicate (not sql\false\`), the hand-written visibilityPredicate is gone from the read path, and the DB equivalence sweep (accessibleByDrizzleability.can for read Workspace`) is green. Steps 1-2 are fast file/grep guards; step 3 runs the committed equivalence dbtest against a live DB.

Prerequisites

Step 1: the read Workspace rule + visibility mapping (WR-1 / WR-2)

set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"

# WR-1: a read Workspace rule exists (both arms).
AB=packages/permissions/src/ability.ts
if grep -q "can('read', 'Workspace'" "$AB" || grep -q "can('read','Workspace'" "$AB"; then
  warden_pass "a read Workspace ability rule is present"
else
  warden_fail "no read Workspace rule in ability.ts — non-admin workspace reads would 403"
fi

# WR-2 (load-bearing): visibility mapped in SUBJECT_TABLES.Workspace, else the
# open arm compiles to sql`false` and every open-but-non-member read silently 404s.
ST=server/src/core/authz/subject-tables.ts
# The mapping is distinctive — it appears only in the Workspace subject-table
# entry — so a direct grep is sufficient and robust against inline-object
# formatting (an awk block-range closes early on the first `}`).
if grep -q "visibility: workspaces.visibility" "$ST"; then
  warden_pass "SUBJECT_TABLES.Workspace maps the visibility column (open arm is load-bearing)"
else
  warden_fail "SUBJECT_TABLES.Workspace is missing visibility — the read-Workspace open arm no-ops to false"
fi

Step 2: the hand-written read predicate is retired (WR-3)

set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"

# The LIST read must run through accessibleByDrizzle now, not the hand-rolled
# visibilityPredicate + isPrivileged-for-reads branch.
RT=server/src/modules/workspaces/routes.ts
if grep -q "accessibleByDrizzle" "$RT" && ! grep -q "visibilityPredicate" "$RT"; then
  warden_pass "workspaces LIST reads via accessibleByDrizzle; hand-written visibilityPredicate retired"
else
  warden_fail "workspaces routes still carry the hand-written visibilityPredicate (read not migrated)"
fi

Step 3: read Workspace ≡ retired predicate, against a live DB (WR-4)

set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# shellcheck disable=SC1091
source "${WARDEN_ENV_FILE:?WARDEN_ENV_FILE not set — run this plan via .warden/run.sh}"

# The equivalence dbtest sweeps accessibleByDrizzle vs ability.can across a
# role × visibility × membership principal matrix (incl. read Workspace), and the
# route suite pins the LIST-hiding + detail/roster 404 tightening.
if pnpm --filter @robin/server exec vitest run \
     src/core/authz/equivalence.dbtest.test.ts src/modules/workspaces/routes.test.ts \
     >/tmp/warden-wsreads.log 2>&1; then
  warden_pass "read Workspace equivalence + workspaces route reads pass against a live DB (WR-4)"
else
  tail -20 /tmp/warden-wsreads.log
  warden_fail "workspace-reads equivalence/route suite FAILED — see /tmp/warden-wsreads.log"
fi

Shape (note for the next author)

Backend sprint, no web surface — no agent-browser E2E. The acceptance core (D36 equivalence) is a committed dbtest CI already runs; step 3 re-runs it under warden as the accumulating regression floor. The detail/roster reads were deliberately tightened (non-member of a private workspace → 404, closing a latent by-id read leak) — the route suite covers that 404; consumer impact was orchestrator-verified low (only an admin roster drawer + LIST-sourced switcher ids consume these endpoints).