Warden plan
tier: volatile requires: []
The quality-gate substrate the greenlight sprint shipped still holds: all seven local gates exit 0 (typecheck, lint, dependency-cruiser, module docs, OpenAPI drift, dependency audit, full DB-backed test suite), the CI workflow still carries the seven exactly-named required-check jobs with the hardening invariants (filter-free triggers, no stale action majors, turbo-cache-proof --force), and the importable branch rulesets still byte-match those check names with the GitHub Actions app pinned. This is the program's baseline regression plan: every later sprint re-runs it before merging.
greenlight-pg (pgvector/pgvector:pg17, host port 5433, db robin_ci with the vector extension) and greenlight-redis (redis:7, host port 6380). Recreate commands: warden.config.sh PREFLIGHT comments or .warden/HANDOFF.md.pnpm install done and workspace dists built (pnpm --filter './packages/*' --filter @robin/server build) — bare tsc false-reds without dists.git diff server/openapi.json reflects only what the generator produces (the drift step restores the file afterwards).set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
command -v pnpm >/dev/null 2>&1 \
&& warden_pass "pnpm available ($(pnpm --version))" \
|| warden_fail "pnpm not on PATH"
command -v jq >/dev/null 2>&1 \
&& warden_pass "jq available" \
|| warden_fail "jq not on PATH"
if docker exec greenlight-pg pg_isready -U postgres -q 2>/dev/null; then
warden_pass "greenlight-pg (pgvector, host 5433) accepting connections"
else
warden_fail "greenlight-pg not running — see .warden/HANDOFF.md 'What's running'"
fi
if docker exec greenlight-redis redis-cli PING 2>/dev/null | grep -q PONG; then
warden_pass "greenlight-redis (host 6380) responds PONG"
else
warden_fail "greenlight-redis not running — see .warden/HANDOFF.md 'What's running'"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# Turbo gates always --force: turbo cache false-greens across worktrees.
if pnpm typecheck --force > /tmp/warden-typecheck.log 2>&1; then
warden_pass "pnpm typecheck --force exits 0 (all workspaces incl. app)"
else
tail -30 /tmp/warden-typecheck.log
warden_fail "typecheck red — /tmp/warden-typecheck.log"
fi
if pnpm lint --force > /tmp/warden-lint.log 2>&1; then
warden_pass "pnpm lint --force exits 0 (10 workspaces: biome + eslint)"
else
tail -30 /tmp/warden-lint.log
warden_fail "lint red — /tmp/warden-lint.log"
fi
if pnpm depcruise > /tmp/warden-depcruise.log 2>&1; then
MODS=$(grep -oE '[0-9]+ modules cruised' /tmp/warden-depcruise.log | head -1)
warden_pass "pnpm depcruise exits 0 (${MODS:-count unknown}; server + 8 packages)"
else
tail -30 /tmp/warden-depcruise.log
warden_fail "depcruise violations — /tmp/warden-depcruise.log"
fi
if pnpm docs:modules > /tmp/warden-docs.log 2>&1; then
warden_pass "pnpm docs:modules exits 0 (module doc gate)"
else
tail -30 /tmp/warden-docs.log
warden_fail "module doc gate red — /tmp/warden-docs.log"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# The generator needs the same env the test suite uses (module import side
# effects validate env). Fake CI values from .github/workflows/pre-merge.yml,
# ports remapped to the warden containers. DB URLs are overridable so a
# sprint gate can point the suite at a dedicated database (e.g.
# robin_ci_warden) without touching the shared robin_ci.
export DATABASE_URL="${DATABASE_URL:-postgresql://postgres:postgres@localhost:5433/robin_ci}"
export ADMIN_DATABASE_URL="${ADMIN_DATABASE_URL:-postgresql://postgres:postgres@localhost:5433/robin_ci}"
export REDIS_URL="redis://localhost:6380"
export BETTER_AUTH_SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export MASTER_KEY="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export KEY_ENCRYPTION_SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export JOB_SIGNING_SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export RECOVERY_SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export INITIAL_USERNAME="ci@robin.test"
export INITIAL_PASSWORD="ci-password-123"
export OPENROUTER_API_KEY="sk-or-ci-fake"
if pnpm --filter @robin/server openapi > /tmp/warden-openapi.log 2>&1; then
warden_pass "openapi manifest generates (exit 0)"
else
tail -30 /tmp/warden-openapi.log
warden_fail "openapi generation failed — /tmp/warden-openapi.log"
fi
if git diff --exit-code server/openapi.json > /dev/null 2>&1; then
warden_pass "server/openapi.json has no drift"
else
git checkout -- server/openapi.json
warden_fail "openapi.json drifted from committed state (restored)"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
# Time-sensitive by design: a fresh upstream advisory reds this on an
# unchanged lockfile ("nothing ships on red" — recorded program decision).
if pnpm audit:gate > /tmp/warden-audit.log 2>&1; then
warden_pass "pnpm audit:gate exits 0 (moderate+, ignoreGhsas honored)"
else
tail -20 /tmp/warden-audit.log
warden_fail "audit gate red — new advisory or regression; /tmp/warden-audit.log"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
export DATABASE_URL="${DATABASE_URL:-postgresql://postgres:postgres@localhost:5433/robin_ci}"
export ADMIN_DATABASE_URL="${ADMIN_DATABASE_URL:-postgresql://postgres:postgres@localhost:5433/robin_ci}"
export REDIS_URL="redis://localhost:6380"
export BETTER_AUTH_SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export MASTER_KEY="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export KEY_ENCRYPTION_SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export JOB_SIGNING_SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export RECOVERY_SECRET="0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
export INITIAL_USERNAME="ci@robin.test"
export INITIAL_PASSWORD="ci-password-123"
export OPENROUTER_API_KEY="sk-or-ci-fake"
# Shuffle stays ON (vitest config); flakes reproduce under --sequence.seed.
if pnpm test --force > /tmp/warden-test.log 2>&1; then
TOTALS=$(grep -E 'Tests .* passed' /tmp/warden-test.log | tail -1)
warden_pass "pnpm test --force exits 0 (full suite; ${TOTALS:-totals in log})"
else
tail -40 /tmp/warden-test.log
warden_fail "test suite red — /tmp/warden-test.log"
fi
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
WF=.github/workflows/pre-merge.yml
NAMES=(
"db:push (ephemeral Postgres)"
"server boot health (/health)"
"app build against server /openapi.json"
"verify (typecheck + test)"
"boundary (dep-cruiser + module docs)"
"lint (biome + eslint)"
"audit (pnpm audit, moderate+)"
)
MISSING=0
for n in "${NAMES[@]}"; do
grep -qF "name: $n" "$WF" || { MISSING=1; echo "missing job name: $n"; }
done
[ "$MISSING" -eq 0 ] \
&& warden_pass "all 7 required-check job names present verbatim in pre-merge.yml" \
|| warden_fail "required-check job name(s) missing/renamed in pre-merge.yml"
[ "$(grep -c 'paths:' "$WF")" -eq 0 ] \
&& warden_pass "workflow is filter-free (no paths: filters)" \
|| warden_fail "paths: filter found — a skipped required workflow hard-blocks PRs"
[ "$(grep -c '@v4' "$WF")" -eq 0 ] \
&& warden_pass "no stale @v4 action pins" \
|| warden_fail "@v4 action pin found (node20 generation retired)"
grep -q 'pnpm typecheck --force' "$WF" && grep -q 'pnpm test --force' "$WF" \
&& warden_pass "verify job is --force-hardened (typecheck + test)" \
|| warden_fail "verify job lost --force hardening (turbo-cache false-green risk)"
[ "$(grep -c 'pnpm lint --force' lefthook.yml)" -eq 1 ] \
&& warden_pass "lefthook pre-push carries the lint gate (CI parity)" \
|| warden_fail "lefthook pre-push lint gate missing"
set -uo pipefail
source "$WARDEN_LIB/assert.sh"
cd "${PROJECT_ROOT:-$(git rev-parse --show-toplevel)}"
CAN=.github/rulesets/canary-required-checks.json
MAIN=.github/rulesets/main-required-checks.json
jq empty "$CAN" 2>/dev/null && jq empty "$MAIN" 2>/dev/null \
&& warden_pass "both ruleset JSONs parse" \
|| warden_fail "ruleset JSON parse failure"
for f in "$CAN" "$MAIN"; do
CTX=$(jq -r '[.. | .context? // empty] | length' "$f")
PIN=$(jq -r '[.. | .integration_id? // empty | select(. == 15368)] | length' "$f")
if [ "$CTX" -eq 7 ] && [ "$PIN" -eq 7 ]; then
warden_pass "$(basename "$f"): 7 contexts, all pinned to GitHub Actions app"
else
warden_fail "$(basename "$f"): contexts=$CTX pinned=$PIN (want 7/7)"
fi
done
CAN_APPR=$(jq -r '[.. | .required_approving_review_count? // empty] | first' "$CAN")
MAIN_APPR=$(jq -r '[.. | .required_approving_review_count? // empty] | first' "$MAIN")
[ "$CAN_APPR" = "0" ] && [ "$MAIN_APPR" = "1" ] \
&& warden_pass "approvals encode the merge policy (canary 0, main 1)" \
|| warden_fail "approval counts drifted (canary=$CAN_APPR main=$MAIN_APPR; want 0/1)"
# Contexts must byte-match the workflow JOB names — job-level `name:` is
# exactly 4-space indented; deeper matches are step/artifact names.
DIFF=$(diff \
<(jq -r '.. | .context? // empty' "$CAN" | sort) \
<(grep -E '^ name: ' .github/workflows/pre-merge.yml \
| sed 's/^ name: //' | sort) )
[ -z "$DIFF" ] \
&& warden_pass "ruleset contexts byte-match the 7 workflow job names" \
|| { echo "$DIFF"; warden_fail "ruleset contexts diverge from workflow job names"; }