Deployment Preflight Runbook
This runbook turns the roadmap's deployment-foundation work into a repeatable launch-readiness check. It does not replace the Era C exit gate; it verifies that the deployed environment is ready to start collecting trustworthy staging or production evidence.
Commands
Controlled public beta / staging rehearsal:
pnpm launch:preflight
Production launch review:
pnpm launch:preflight:production
One-command launch evidence refresh:
pnpm launch:evidence -- --base-url=https://<deploy-host> --iiif-tile-url=https://<iiif-tile>
pnpm launch:evidence:production -- --base-url=https://<shareable-url> --public-read-base-url=https://<shareable-url> --iiif-tile-url=https://<iiif-tile> --validation-service-url=https://metamuseum-validation.onrender.com/validate --reconciliation-service-url=https://metamuseum-reconciliation.onrender.com
Artifacts:
- `artifacts/launch/deployment-preflight-latest.json`
- `artifacts/launch/runs/deployment-preflight-<timestamp>.json`
For production-target runs, localhost/private deployment targets are treated as
operator diagnostics. The command still writes the timestamped run artifact so
the failure can be inspected, but it does not replace
`deployment-preflight-latest.json` when production URL checks show `localhost`,
`127.0.0.1`, or `::1`.
The preflight, storage export, DR drill, launch review, and staging explore-smoke scripts load local `.env*` files for operator convenience, but explicit shell or CI environment variables always win.
For production evidence runs from a workstation that also has staging-only
values such as `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN`, set
`METAMUSEUM_SKIP_ENV_FILES=1` and provide the required production variables
explicitly so local `.env*` files cannot pollute the preflight artifact.
What It Checks
the configured public base URL, so auth readiness is live-route evidence
rather than only secret presence.
`METAMUSEUM_SECRET_ROTATION_CONFIRMED_AT` must be current, and
`METAMUSEUM_SECRET_ROTATION_KEYS` must name the launch-critical keys rotated
after any exposure.
checked `.env*` paths, tracked sensitive filenames, and matching commit hashes;
production fails if sensitive env files are tracked, still appear in history,
or the git probe cannot run.
`METAMUSEUM_PUBLIC_READ_BASE_URL` set to the same public URL, so absolute
Open Graph image URLs do not point crawlers at a protected preview host.
Equal localhost or non-HTTPS values are still production failures because
crawler debug tools cannot fetch private origins.
preflight unless `CRON_SECRET` and `METAMUSEUM_OUTBOX_CRON_ENABLED=1` are set,
and live MediaWiki/Wikibase publication endpoints or bot tokens fail unless
`CRON_SECRET` and `METAMUSEUM_PUBLISH_QUEUE_CRON_ENABLED=1` are set.
`pnpm projection:readiness` records record count, discovery-workflow count,
graph-workflow count, and search/graph p95 metrics as `portable`, `watch`, or
`enable` / `enabled` evidence.
the local fallback/in-process behavior, while configured URLs must have a
`/health` probe with status and duration so cold starts and timeouts appear
in launch evidence. The long external-service probe timeout is only evidence
capture; production strict `/api/validate` is closed by default unless
`METAMUSEUM_STRICT_VALIDATION_PUBLIC=1` is set. A public Render-hosted strict
validator also requires paid/no-sleep capacity and a passing user-facing
health budget.
production probe-based uptime must target the final public HTTPS deployment
URL, not localhost or another private/non-HTTPS origin.
- Storage resolves to Postgres-backed mode via `DATABASE_URL` and `METAMUSEUM_STORAGE_MODE`.
- Database SSL mode is pinned to `sslmode=verify-full`; staging warns on `require`/missing values, and production fails until full verification is explicit.
- Database authentication is live-probed with `SELECT 1`; production fails `runtime.databaseConnection` if the rotated `DATABASE_URL` cannot authenticate from the preflight environment, so a parseable URL cannot hide a stale Vercel or CI secret.
- Active Vercel deployment metadata is captured without secret values; production fails `runtime.deploymentActivation` when the active deployment is missing, not `READY`, unparsable, or older than `METAMUSEUM_SECRET_ROTATION_CONFIRMED_AT`, so updating Vercel env vars without redeploying cannot satisfy strict launch evidence.
- Auth.js launch secrets are present and not using the development fallback.
- GitHub OAuth credentials are present for sign-in; failures name the exact missing variable without exposing secret values.
- Auth.js deployed routes answer `/api/auth/signin` and `/api/auth/session` on
- Secret rotation evidence is recorded without secret values:
- Sensitive env-file history is checked from git: preflight records only the
- Public base URL is configured for smoke, uptime, and SLO commands.
- Production social-preview metadata has both `BASE_URL` and
- Worker scheduler readiness is explicit: projection targets fail production
- Projection scale readiness is explicit before target flags are enabled:
- The configured public-read deployment answers `/`, `/api/health`, and `/api/records` with a non-empty records payload, so Vercel Deployment Protection and managed-storage seed failures are not blocking launch-critical public reads.
- Optional Render validation/reconciliation mode is explicit: unset URLs record
- Public-read uptime source is configured through Prometheus or scheduled probes;
- k6 has a real deployed target and an explicit, fetchable `IIIF_TILE_URL` for launch SLO evidence.
- Latest DR drill artifact is fresh and has a verified Postgres restore rehearsal.
- Test-role override secrets are available for staging smoke, pass a basic secret-quality check, and are absent from production.
Required Environment Surface
Minimum public-beta/prod variables:
shell/CI variables instead of local `.env*` files.
latest exposed-secret rotation.
`AUTH_SECRET`, `AUTH_GITHUB_SECRET`, `DATABASE_URL`, `ANTHROPIC_API_KEY`,
`SMITHSONIAN_API_KEY`, and `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN` after those
values have been rotated in local, CI, and deployment secret stores.
`OUTBOX_PROJECT_TO_GRAPHDB=1`.
`METAMUSEUM_MEDIAWIKI_` or `METAMUSEUM_WIKIBASE_` endpoints/tokens are set.
- `DATABASE_URL` with `sslmode=verify-full`
- `METAMUSEUM_STORAGE_MODE=postgres`
- `METAMUSEUM_SKIP_ENV_FILES=1` when running production preflight from explicit
- `AUTH_SECRET`
- `AUTH_GITHUB_ID`
- `AUTH_GITHUB_SECRET`
- `METAMUSEUM_SECRET_ROTATION_CONFIRMED_AT` as an ISO timestamp or date for the
- `METAMUSEUM_SECRET_ROTATION_KEYS` as a comma-separated list including
- `BASE_URL`
- `METAMUSEUM_PUBLIC_READ_BASE_URL` set to the same public URL as `BASE_URL`
- `METAMUSEUM_UPTIME_PROMETHEUS_URL` or scheduled probe config via `METAMUSEUM_PUBLIC_READ_BASE_URL` set to the final public HTTPS deployment URL
- `IIIF_TILE_URL`
- `CRON_SECRET` when projection or live publication scale flags are enabled.
- `METAMUSEUM_OUTBOX_CRON_ENABLED=1` before `OUTBOX_PROJECT_TO_SOLR=1` or
- `METAMUSEUM_PUBLISH_QUEUE_CRON_ENABLED=1` before live
Optional external service evidence:
intentionally public. Leave unset/off for operator-only validation; production
route handling returns `404` by default.
cold starts need a longer readiness window.
validation service off sleeping/free-tier capacity before exposing strict
`/api/validate` to production users.
user-facing strict-validation health budget. This stays separate from
`METAMUSEUM_EXTERNAL_SERVICE_PROBE_TIMEOUT_MS`, which may remain high enough
to capture cold-start evidence.
calls; strict validation returns `502` instead of waiting indefinitely.
- `VALIDATION_SERVICE_URL=https://metamuseum-validation.onrender.com/validate` for the Render validation service `/validate` URL.
- `METAMUSEUM_STRICT_VALIDATION_PUBLIC=1` only when strict `/api/validate` is
- `RECONCILIATION_SERVICE_URL=https://metamuseum-reconciliation.onrender.com` for the Render reconciliation service base URL.
- `METAMUSEUM_EXTERNAL_SERVICE_PROBE_TIMEOUT_MS` (default `45000`) when Render
- `METAMUSEUM_VALIDATION_SERVICE_CAPACITY=paid` after upgrading the Render
- `METAMUSEUM_VALIDATION_SERVICE_MAX_HEALTH_MS` (default `5000`) for the
- `VALIDATION_TIMEOUT_MS` (default `15000`) for runtime `/api/validate` proxy
When `VALIDATION_SERVICE_URL` is unset, non-strict import/inspect flows use the
local Linked Art profile fallback after the localhost validation default is
unavailable; strict `/api/validate` still requires a running validation service
and is closed in production unless `METAMUSEUM_STRICT_VALIDATION_PUBLIC=1`.
When `RECONCILIATION_SERVICE_URL` is unset, the app uses its in-process
deterministic reconciliation and optional LLM tiebreaker.
Staging-only smoke support:
- `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN` (generated by `pnpm launch:smoke-token`; same value on server and smoke runner)
Launch Review Sequence
- Configure the deployed environment and secrets.
- For staging, run `pnpm launch:smoke-token` once per token rotation; it updates local `.env` without printing the value, so copy `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN` from the env file into both the staging server and smoke runner.
- Run `pnpm storage:export:postgres` after setting `DATABASE_URL`; the exporter uses the same managed-document contract as `src/utils/storage.ts`.
- Run `pnpm dr:drill` against the configured storage mode.
- Before enabling Solr/GraphDB projection flags, run `pnpm projection:readiness -- --active-discovery-workflows=<count> --active-graph-workflows=<count> --search-p95-ms=<ms> --graph-p95-ms=<ms>`.
- Before production preflight, finish the credential-rotation runbook and keep
the non-secret rotation stamp/key list in the deployment env. Then run
`pnpm launch:preflight` or `pnpm launch:preflight:production`; the command
probes the configured public URL to verify `/`, `/api/health`,
`/api/records`, `/api/auth/signin`, and `/api/auth/session` are reachable
without Vercel Authentication, fetches the configured `IIIF_TILE_URL`, probes configured
Render validation/reconciliation services at `/health`, checks sensitive
`.env*` tracking/history via git without printing secret values, captures
active Vercel deployment id/state/createdAt metadata, and, for production,
fails if `BASE_URL` and `METAMUSEUM_PUBLIC_READ_BASE_URL` are not the same
shareable public HTTPS URL or the active deployment predates the latest
launch-critical secret rotation. If `VALIDATION_SERVICE_URL` points at
Render for production, the same preflight also fails sleeping/free-tier
validation capacity or `/health` durations above the user-facing strict
validation budget even when the long cold-start probe eventually passes.
- Prefer the evidence runner when refreshing the full packet:
- Or run public smoke checks manually:
- Collect performance and exit-gate evidence if not using the runner:
- Generate the launch decision packet if not using the runner:
- After every domain change, redeploy and refresh the homepage in Facebook
Sharing Debugger plus the equivalent LinkedIn/X preview tools before
circulating public links.
- `pnpm launch:evidence -- --base-url=https://<deploy-host> --iiif-tile-url=https://<iiif-tile>`
- `pnpm launch:evidence:production -- --base-url=https://<shareable-url> --public-read-base-url=https://<shareable-url> --iiif-tile-url=https://<iiif-tile> --validation-service-url=https://metamuseum-validation.onrender.com/validate --reconciliation-service-url=https://metamuseum-reconciliation.onrender.com`
- The runner writes `artifacts/launch/launch-evidence-latest.json`, keeps going after individual command failures, and records which evidence steps still block launch.
- `BASE_URL=https://<deploy-host> pnpm smoke:public-trust`
- `BASE_URL=https://<deploy-host> pnpm smoke:crawler-preview` writes `artifacts/launch/crawler-preview-latest.json` and verifies Facebook, Slack, Googlebot, LinkedIn, and X/Twitter-facing preview metadata plus image fetchability.
- `BASE_URL=https://<deploy-host> pnpm a11y:check` writes `artifacts/launch/a11y-latest.json`.
- `BASE_URL=https://<deploy-host> pnpm smoke:explore:matrix` writes `artifacts/launch/explore-smoke-latest.json` for staging researcher flow.
- `BASE_URL=https://<deploy-host> IIIF_TILE_URL=https://iiif.micr.io/RFwqO/full/max/0/default.jpg pnpm k6:slo`
- `pnpm era-c:exit-gate:evidence`
- `pnpm launch:review` for controlled beta / staging.
- `pnpm launch:beta:readiness` for the controlled beta go/no-go summary.
- `pnpm launch:review:production` for fail-fast production review.
Pass Criteria
- Staging can proceed with warnings only when the warnings are explicitly accepted for rehearsal.
- Production must have `status: "pass"` in `deployment-preflight-latest.json`.
- Passing deployment preflight means the environment is ready to collect evidence; it does not mean the 30-day SLO, uptime, adoption, or KPI gates have passed.
- Passing launch review requires fresh launch evidence across hardening security/DR, preflight, exit-gate, crawler preview, public-trust, a11y, and explore import checks; `pnpm launch:evidence` refreshes that packet and records any remaining blockers. See `docs/ops/launch-review.md`.
- Passing controlled beta readiness means staging can go live with only accepted evidence-collection warnings; production still requires `pnpm launch:review:production`.