Warden plan
tier: needs-postgres requires: []
The capture sprint's contract holds deterministically — no live LLM, no live server. Schema: the attachments table exists with its committed migration 0008. Queue: the transcription seam (QUEUE_NAMES.transcription + enqueueTranscription) is present. Adapter: the STT service reads env lazily off process.env (C-23) and never imports bootstrap/env. Authz: the capture scope is registered on the authorization server, core/authz/bearer.ts is the exported bearer choke-point, and /capture/oauth-config is public in BOTH allowlist mirrors. OpenAPI: /capture/uploads is multipart and the oauth2 flows point at /api/auth/oauth2/* (never the retired mcp/authorize). Boot: RESEND_API_KEY is required. Finally the two committed integration suites (PKCE token flow, upload lifecycle) run green against the branch.
Module isolation (modules never import core/oauth) is enforced by dependency-cruiser and already gated by plan 01's pnpm depcruise — cited here, not re-asserted.
PROJECT_ROOT = the capture worktree (invoke via its .warden symlink).pnpm, jq, 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)}"
# Attachments table in the Drizzle schema.
if grep -Eq "attachments[[:space:]]*=[[:space:]]*pgTable" server/src/db/schema.ts; then
warden_pass "attachments pgTable present in server/src/db/schema.ts"
else
warden_fail "attachments pgTable missing from db schema — capture storage gone"
fi
# Committed migration 0008 creates it.
MIG=$(ls server/drizzle/migrations/0008_*.sql 2>/dev/null | head -1)
if [[ -n "$MIG" ]] && grep -q 'CREATE TABLE "attachments"' "$MIG"; then
warden_pass "migration 0008 creates attachments ($(basename "$MIG"))"
else
warden_fail "migration 0008 missing or no longer creates attachments"
fi
# Transcription queue name registered.
Q=packages/queue/src/index.ts
if grep -Eq "transcription:[[:space:]]*['\"]transcription['\"]" "$Q"; then
warden_pass "QUEUE_NAMES.transcription registered in @robin/queue"
else
warden_fail "QUEUE_NAMES.transcription gone from @robin/queue — worker seam broken"
fi
# Enqueue helper present (manual-snapshot: the seam capture routes call).
if grep -q "enqueueTranscription" "$Q"; then
warden_pass "enqueueTranscription helper exported by @robin/queue"
else
warden_fail "enqueueTranscription missing — upload can no longer hand off to STT"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# STT adapter reads process.env lazily (C-23) — no bootstrap/env import,
# so a zero-capture-env boot still survives.
TS=server/src/services/transcription.ts
if grep -q "process.env.STT_API_BASE_URL" "$TS" && ! grep -q "bootstrap/env" "$TS"; then
warden_pass "STT adapter reads process.env lazily, no bootstrap/env import (C-23)"
else
warden_fail "STT adapter env discipline broken — eager env or bootstrap/env import"
fi
# RESEND_API_KEY is boot-required.
if grep -q "RESEND_API_KEY" server/src/bootstrap/env.ts; then
warden_pass "RESEND_API_KEY declared in bootstrap/env.ts"
else
warden_fail "RESEND_API_KEY no longer in bootstrap/env.ts — boot contract drifted"
fi
# Both .env.example mirrors exist.
if [[ -f server/.env.example && -f app/.env.example ]]; then
warden_pass "server/.env.example and app/.env.example both present"
else
warden_fail ".env.example mirror missing (server or app)"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# `capture` scope registered on the authorization server.
if grep -Eq "['\"]capture['\"]" server/src/modules/iam/auth.ts; then
warden_pass "capture scope registered in iam/auth.ts (AS scopes)"
else
warden_fail "capture scope gone from iam/auth.ts — PKCE clients can't request it"
fi
# Bearer choke-point exists and is exported from the authz barrel.
if [[ -f server/src/core/authz/bearer.ts ]] && grep -q "captureBearerAuth" server/src/core/authz/index.ts; then
warden_pass "core/authz/bearer.ts present + captureBearerAuth exported (modules-never-import-core/oauth covered by depcruise, plan 01)"
else
warden_fail "capture bearer choke-point missing or unexported from core/authz"
fi
# /capture/oauth-config public in BOTH allowlist mirrors.
if grep -q "/capture/oauth-config" server/src/bootstrap/assert-prod-safety.ts \
&& grep -q "/capture/oauth-config" server/src/__tests__/route-allowlist.test.ts; then
warden_pass "/capture/oauth-config in PUBLIC_ROUTES, both mirrors (prod-safety + allowlist test)"
else
warden_fail "/capture/oauth-config missing from a PUBLIC_ROUTES mirror — discovery would 401"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
OA=server/openapi.json
# Multipart upload surface + capture scope documented.
if jq -e '.paths["/capture/uploads"].post.requestBody.content["multipart/form-data"]' "$OA" >/dev/null \
&& jq -e '.components.securitySchemes.oauth2.flows.authorizationCode.scopes.capture' "$OA" >/dev/null; then
warden_pass "openapi: /capture/uploads is multipart + capture scope documented"
else
warden_fail "openapi drift: /capture/uploads multipart body or capture scope missing"
fi
# OAuth2 flow URLs point at the colocated AS.
if jq -e '.components.securitySchemes.oauth2.flows.authorizationCode
| select(.authorizationUrl == "/api/auth/oauth2/authorize"
and .tokenUrl == "/api/auth/oauth2/token")' "$OA" >/dev/null; then
warden_pass "openapi: oauth2 flow URLs are /api/auth/oauth2/{authorize,token}"
else
warden_fail "openapi: oauth2 flow URLs drifted off /api/auth/oauth2/*"
fi
# Retired MCP authorize endpoint must NOT resurface.
if ! grep -q "mcp/authorize" "$OA"; then
warden_pass "openapi: no mcp/authorize anywhere (retired endpoint stays retired)"
else
warden_fail "openapi: mcp/authorize resurfaced — clients would bind to the retired AS"
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/__tests__/capture-oauth-pkce.integration.test.ts \
src/__tests__/capture-upload.integration.test.ts \
>/tmp/warden-capture.log 2>&1; then
warden_pass "capture PKCE + upload integration suites pass (token flow, multipart lifecycle, retry, public discovery)"
else
tail -20 /tmp/warden-capture.log
warden_fail "capture integration tests FAILED — see /tmp/warden-capture.log"
fi
Backend-only sprint: zero app code (only app/.env.example), so no agent-browser leg. The authz seam is core/authz/bearer.ts (verification via @robin/oauth, table-less by depcruise packages-own-no-table); the STT adapter is deliberately env-lazy so boot works with zero capture env. If the upload route or worker is refactored, keep the enqueueTranscription handoff and the multipart contract — MCP clients bind to openapi.json.