Warden plan
tier: needs-postgres requires: []
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.
PROJECT_ROOT = the d30-gate worktree (invoke via its .warden symlink).pnpm, grep on PATH. The env file is provisioned automatically by .warden/run.sh from the tracked .warden/env/ci-env.template.sh plus the machine-local secrets file ~/.config/robin/warden-secrets.sh (mode 600, outside every git repository; override with WARDEN_SECRETS_FILE); first-time setup: mkdir -p ~/.config/robin && cp .warden/env/secrets.local.example.sh ~/.config/robin/warden-secrets.sh && chmod 600 ~/.config/robin/warden-secrets.sh.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
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
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).