diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b54c429b6..7543490b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,6 +87,7 @@ jobs: env: SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + RAG_QUERY_HASH_SECRET: ${{ secrets.RAG_QUERY_HASH_SECRET }} run: npm run check:deployment-readiness # Chromium browser smoke in parallel with verify, so the slowest check no diff --git a/docs/deployment-architecture.md b/docs/deployment-architecture.md index 579f9366a..3bb2ccbac 100644 --- a/docs/deployment-architecture.md +++ b/docs/deployment-architecture.md @@ -184,7 +184,7 @@ Operational rules that follow: | `OPENAI_API_KEY` | **critical** | runtime | host secret store; GitHub repo secret | | `SUPABASE_PROJECT_REF` / `SUPABASE_PROJECT_NAME` | low | runtime | plain env (pins `check:supabase-project`) | | `INDEXING_V3_AGENT_SECRET` | high | runtime | Supabase Edge Function secrets | -| `RAG_QUERY_HASH_SECRET` | high | runtime | host secret store | +| `RAG_QUERY_HASH_SECRET` | high | runtime | host secret store; GitHub repo secret (CI boot smoke) | | `E2E_USER_EMAIL` / `E2E_USER_PASSWORD` | medium | CI only | GitHub repo secrets | Rules: diff --git a/docs/observability-slos.md b/docs/observability-slos.md index 67648b28f..519458bd7 100644 --- a/docs/observability-slos.md +++ b/docs/observability-slos.md @@ -146,10 +146,10 @@ Failing loudly: `eval-canary` (or comments on the existing open one), so a regression creates a durable, assignable artifact rather than a missed notification. -Required repo secrets (same ones CI's deployment boot smoke already uses, -plus the eval owner): `SUPABASE_SERVICE_ROLE_KEY`, `OPENAI_API_KEY`, +Required repo secrets: `SUPABASE_SERVICE_ROLE_KEY`, `OPENAI_API_KEY`, `E2E_USER_EMAIL` (resolved to the eval owner via `RAG_EVAL_OWNER_EMAIL`). The workflow preflights these and fails with an explicit message when absent. +The workflow preflights these and fails with an explicit message when absent. Operational notes: diff --git a/scripts/deployment-boot-smoke.mjs b/scripts/deployment-boot-smoke.mjs index 66950e5b0..9ce7b56f3 100644 --- a/scripts/deployment-boot-smoke.mjs +++ b/scripts/deployment-boot-smoke.mjs @@ -27,7 +27,7 @@ const pollDelayMs = parsePositiveInt("DEPLOY_SMOKE_POLL_DELAY_MS", 1000); const logRoot = mkdtempSync(resolve(tmpdir(), "clinical-kb-deploy-smoke-")); const logPath = resolve(logRoot, "deploy-smoke.log"); const nextBin = resolve(projectRoot, "node_modules", "next", "dist", "bin", "next"); -const requiredProductionEnv = ["SUPABASE_SERVICE_ROLE_KEY", "OPENAI_API_KEY"]; +const requiredProductionEnv = ["SUPABASE_SERVICE_ROLE_KEY", "OPENAI_API_KEY", "RAG_QUERY_HASH_SECRET"]; if (!existsSync(nextBin)) { throw new Error(`Next.js binary not found at: ${nextBin}`); @@ -118,10 +118,10 @@ async function bootSmoke() { ...process.env, PORT: String(port), NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? "https://sjrfecxgysukkwxsowpy.supabase.co", - // instrumentation.ts register() requires these in production mode; provide - // placeholder values so the boot-smoke can verify server identity without - // needing real secrets. Routes that actually use Supabase/OpenAI will still - // fail with real errors, but /api/local-project-id does not. + // instrumentation.ts register() requires these in production mode. Supabase + // and OpenAI keep placeholder fallbacks for local smoke runs; the query-hash + // secret is required upfront via requiredProductionEnv (no placeholder) so + // main/release CI cannot pass without the real repository secret. SUPABASE_SERVICE_ROLE_KEY: process.env.SUPABASE_SERVICE_ROLE_KEY ?? "placeholder-ci-service-role", OPENAI_API_KEY: process.env.OPENAI_API_KEY ?? "placeholder-ci-openai", },