Warden plan

09 - root-workspace internal-only gate (D30)

← eval suite index


tier: needs-postgres requires: []


09 - root-workspace internal-only gate (D30)

What it proves

Root-workspace wikis stay INTERNAL-ONLY (D30): the publish service refuses a root-workspace wiki before any write, and the public reader excludes root-workspace rows even if published=true were set directly. A future refactor that drops either guard reds this plan. Step 1 is a fast grep guard; step 2 runs the committed D30 regression tests against the branch.

Prerequisites

Step 1: both guards present (D30 enforcement locus)

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

# Publish choke-point: the service refuses a root-workspace wiki.
PS=server/src/services/publish.ts
if grep -q "isRoot" "$PS" && grep -q "root-workspace" "$PS"; then
  warden_pass "publish service refuses root-workspace wikis (D30 choke-point)"
else
  warden_fail "publish service no longer guards root-workspace wikis — D30 publish leak"
fi

# Public reader defense-in-depth: excludes root-workspace rows.
PR=server/src/modules/public/routes.ts
if grep -q "isRoot" "$PR"; then
  warden_pass "public reader excludes root-workspace wikis (D30 defense-in-depth)"
else
  warden_fail "public reader no longer excludes root-workspace wikis — a published root row would serve"
fi

Step 2: D30 regression tests pass against the branch

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}"

if pnpm --filter @robin/server exec vitest run \
     src/modules/wikis/wikis.publish.test.ts src/modules/public/public.test.ts \
     >/tmp/warden-d30.log 2>&1; then
  warden_pass "D30 publish-refusal + public-reader-exclusion tests pass (root publish 403, published root 404, non-root unchanged)"
else
  tail -20 /tmp/warden-d30.log
  warden_fail "D30 regression tests FAILED — see /tmp/warden-d30.log"
fi

Shape (note for the next author)

Backend security fix, no web surface. The gate lives in the publish service (covers HTTP + MCP) with the public reader as defense-in-depth. Both guards use workspaces.is_root. If publish or the public reader is refactored, keep the root check — it is the catch that makes D30's sanctioned root cross-workspace citation crossing safe (root wikis may cite any workspace the author can read, so external publish would leak cross-workspace content).