Warden plan

01 - Greenlight quality gates

← eval suite index


tier: volatile requires: []


01 - Greenlight quality gates

What it proves

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.

Prerequisites

Step 1: toolchain + services preflight

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

Step 2: static gates — typecheck, lint, depcruise, module docs

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

Step 3: OpenAPI generate + drift

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

Step 4: dependency audit gate

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

Step 5: full DB-backed test suite

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

Step 6: CI workflow contract

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"

Step 7: ruleset artifacts

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