{"id":"ops/deployment-preflight","relativePath":"ops/deployment-preflight.md","title":"Deployment Preflight Runbook","markdown":"# Deployment Preflight Runbook\n\nThis 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.\n\n## Commands\n\nControlled public beta / staging rehearsal:\n\n```bash\npnpm launch:preflight\n```\n\nProduction launch review:\n\n```bash\npnpm launch:preflight:production\n```\n\nOne-command launch evidence refresh:\n\n```bash\npnpm launch:evidence -- --base-url=https://<deploy-host> --iiif-tile-url=https://<iiif-tile>\npnpm 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\n```\n\nArtifacts:\n\n- `artifacts/launch/deployment-preflight-latest.json`\n- `artifacts/launch/runs/deployment-preflight-<timestamp>.json`\n\nFor production-target runs, localhost/private deployment targets are treated as\noperator diagnostics. The command still writes the timestamped run artifact so\nthe failure can be inspected, but it does not replace\n`deployment-preflight-latest.json` when production URL checks show `localhost`,\n`127.0.0.1`, or `::1`.\n\nThe 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.\nFor production evidence runs from a workstation that also has staging-only\nvalues such as `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN`, set\n`METAMUSEUM_SKIP_ENV_FILES=1` and provide the required production variables\nexplicitly so local `.env*` files cannot pollute the preflight artifact.\n\n## What It Checks\n\n- Storage resolves to Postgres-backed mode via `DATABASE_URL` and `METAMUSEUM_STORAGE_MODE`.\n- Database SSL mode is pinned to `sslmode=verify-full`; staging warns on `require`/missing values, and production fails until full verification is explicit.\n- 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.\n- 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.\n- Auth.js launch secrets are present and not using the development fallback.\n- GitHub OAuth credentials are present for sign-in; failures name the exact missing variable without exposing secret values.\n- Auth.js deployed routes answer `/api/auth/signin` and `/api/auth/session` on\n  the configured public base URL, so auth readiness is live-route evidence\n  rather than only secret presence.\n- Secret rotation evidence is recorded without secret values:\n  `METAMUSEUM_SECRET_ROTATION_CONFIRMED_AT` must be current, and\n  `METAMUSEUM_SECRET_ROTATION_KEYS` must name the launch-critical keys rotated\n  after any exposure.\n- Sensitive env-file history is checked from git: preflight records only the\n  checked `.env*` paths, tracked sensitive filenames, and matching commit hashes;\n  production fails if sensitive env files are tracked, still appear in history,\n  or the git probe cannot run.\n- Public base URL is configured for smoke, uptime, and SLO commands.\n- Production social-preview metadata has both `BASE_URL` and\n  `METAMUSEUM_PUBLIC_READ_BASE_URL` set to the same public URL, so absolute\n  Open Graph image URLs do not point crawlers at a protected preview host.\n  Equal localhost or non-HTTPS values are still production failures because\n  crawler debug tools cannot fetch private origins.\n- Worker scheduler readiness is explicit: projection targets fail production\n  preflight unless `CRON_SECRET` and `METAMUSEUM_OUTBOX_CRON_ENABLED=1` are set,\n  and live MediaWiki/Wikibase publication endpoints or bot tokens fail unless\n  `CRON_SECRET` and `METAMUSEUM_PUBLISH_QUEUE_CRON_ENABLED=1` are set.\n- Projection scale readiness is explicit before target flags are enabled:\n  `pnpm projection:readiness` records record count, discovery-workflow count,\n  graph-workflow count, and search/graph p95 metrics as `portable`, `watch`, or\n  `enable` / `enabled` evidence.\n- 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.\n- Optional Render validation/reconciliation mode is explicit: unset URLs record\n  the local fallback/in-process behavior, while configured URLs must have a\n  `/health` probe with status and duration so cold starts and timeouts appear\n  in launch evidence. The long external-service probe timeout is only evidence\n  capture; production strict `/api/validate` is closed by default unless\n  `METAMUSEUM_STRICT_VALIDATION_PUBLIC=1` is set. A public Render-hosted strict\n  validator also requires paid/no-sleep capacity and a passing user-facing\n  health budget.\n- Public-read uptime source is configured through Prometheus or scheduled probes;\n  production probe-based uptime must target the final public HTTPS deployment\n  URL, not localhost or another private/non-HTTPS origin.\n- k6 has a real deployed target and an explicit, fetchable `IIIF_TILE_URL` for launch SLO evidence.\n- Latest DR drill artifact is fresh and has a verified Postgres restore rehearsal.\n- Test-role override secrets are available for staging smoke, pass a basic secret-quality check, and are absent from production.\n\n## Required Environment Surface\n\nMinimum public-beta/prod variables:\n\n- `DATABASE_URL` with `sslmode=verify-full`\n- `METAMUSEUM_STORAGE_MODE=postgres`\n- `METAMUSEUM_SKIP_ENV_FILES=1` when running production preflight from explicit\n  shell/CI variables instead of local `.env*` files.\n- `AUTH_SECRET`\n- `AUTH_GITHUB_ID`\n- `AUTH_GITHUB_SECRET`\n- `METAMUSEUM_SECRET_ROTATION_CONFIRMED_AT` as an ISO timestamp or date for the\n  latest exposed-secret rotation.\n- `METAMUSEUM_SECRET_ROTATION_KEYS` as a comma-separated list including\n  `AUTH_SECRET`, `AUTH_GITHUB_SECRET`, `DATABASE_URL`, `ANTHROPIC_API_KEY`,\n  `SMITHSONIAN_API_KEY`, and `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN` after those\n  values have been rotated in local, CI, and deployment secret stores.\n- `BASE_URL`\n- `METAMUSEUM_PUBLIC_READ_BASE_URL` set to the same public URL as `BASE_URL`\n- `METAMUSEUM_UPTIME_PROMETHEUS_URL` or scheduled probe config via `METAMUSEUM_PUBLIC_READ_BASE_URL` set to the final public HTTPS deployment URL\n- `IIIF_TILE_URL`\n- `CRON_SECRET` when projection or live publication scale flags are enabled.\n- `METAMUSEUM_OUTBOX_CRON_ENABLED=1` before `OUTBOX_PROJECT_TO_SOLR=1` or\n  `OUTBOX_PROJECT_TO_GRAPHDB=1`.\n- `METAMUSEUM_PUBLISH_QUEUE_CRON_ENABLED=1` before live\n  `METAMUSEUM_MEDIAWIKI_*` or `METAMUSEUM_WIKIBASE_*` endpoints/tokens are set.\n\nOptional external service evidence:\n\n- `VALIDATION_SERVICE_URL=https://metamuseum-validation.onrender.com/validate` for the Render validation service `/validate` URL.\n- `METAMUSEUM_STRICT_VALIDATION_PUBLIC=1` only when strict `/api/validate` is\n  intentionally public. Leave unset/off for operator-only validation; production\n  route handling returns `404` by default.\n- `RECONCILIATION_SERVICE_URL=https://metamuseum-reconciliation.onrender.com` for the Render reconciliation service base URL.\n- `METAMUSEUM_EXTERNAL_SERVICE_PROBE_TIMEOUT_MS` (default `45000`) when Render\n  cold starts need a longer readiness window.\n- `METAMUSEUM_VALIDATION_SERVICE_CAPACITY=paid` after upgrading the Render\n  validation service off sleeping/free-tier capacity before exposing strict\n  `/api/validate` to production users.\n- `METAMUSEUM_VALIDATION_SERVICE_MAX_HEALTH_MS` (default `5000`) for the\n  user-facing strict-validation health budget. This stays separate from\n  `METAMUSEUM_EXTERNAL_SERVICE_PROBE_TIMEOUT_MS`, which may remain high enough\n  to capture cold-start evidence.\n- `VALIDATION_TIMEOUT_MS` (default `15000`) for runtime `/api/validate` proxy\n  calls; strict validation returns `502` instead of waiting indefinitely.\n\nWhen `VALIDATION_SERVICE_URL` is unset, non-strict import/inspect flows use the\nlocal Linked Art profile fallback after the localhost validation default is\nunavailable; strict `/api/validate` still requires a running validation service\nand is closed in production unless `METAMUSEUM_STRICT_VALIDATION_PUBLIC=1`.\nWhen `RECONCILIATION_SERVICE_URL` is unset, the app uses its in-process\ndeterministic reconciliation and optional LLM tiebreaker.\n\nStaging-only smoke support:\n\n- `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN` (generated by `pnpm launch:smoke-token`; same value on server and smoke runner)\n\n## Launch Review Sequence\n\n1. Configure the deployed environment and secrets.\n2. 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.\n3. Run `pnpm storage:export:postgres` after setting `DATABASE_URL`; the exporter uses the same managed-document contract as `src/utils/storage.ts`.\n4. Run `pnpm dr:drill` against the configured storage mode.\n5. 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>`.\n6. Before production preflight, finish the credential-rotation runbook and keep\n   the non-secret rotation stamp/key list in the deployment env. Then run\n   `pnpm launch:preflight` or `pnpm launch:preflight:production`; the command\n   probes the configured public URL to verify `/`, `/api/health`,\n   `/api/records`, `/api/auth/signin`, and `/api/auth/session` are reachable\n   without Vercel Authentication, fetches the configured `IIIF_TILE_URL`, probes configured\n   Render validation/reconciliation services at `/health`, checks sensitive\n   `.env*` tracking/history via git without printing secret values, captures\n   active Vercel deployment id/state/createdAt metadata, and, for production,\n   fails if `BASE_URL` and `METAMUSEUM_PUBLIC_READ_BASE_URL` are not the same\n   shareable public HTTPS URL or the active deployment predates the latest\n   launch-critical secret rotation. If `VALIDATION_SERVICE_URL` points at\n   Render for production, the same preflight also fails sleeping/free-tier\n   validation capacity or `/health` durations above the user-facing strict\n   validation budget even when the long cold-start probe eventually passes.\n7. Prefer the evidence runner when refreshing the full packet:\n   - `pnpm launch:evidence -- --base-url=https://<deploy-host> --iiif-tile-url=https://<iiif-tile>`\n   - `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`\n   - The runner writes `artifacts/launch/launch-evidence-latest.json`, keeps going after individual command failures, and records which evidence steps still block launch.\n8. Or run public smoke checks manually:\n   - `BASE_URL=https://<deploy-host> pnpm smoke:public-trust`\n   - `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.\n   - `BASE_URL=https://<deploy-host> pnpm a11y:check` writes `artifacts/launch/a11y-latest.json`.\n   - `BASE_URL=https://<deploy-host> pnpm smoke:explore:matrix` writes `artifacts/launch/explore-smoke-latest.json` for staging researcher flow.\n9. Collect performance and exit-gate evidence if not using the runner:\n   - `BASE_URL=https://<deploy-host> IIIF_TILE_URL=https://iiif.micr.io/RFwqO/full/max/0/default.jpg pnpm k6:slo`\n   - `pnpm era-c:exit-gate:evidence`\n10. Generate the launch decision packet if not using the runner:\n   - `pnpm launch:review` for controlled beta / staging.\n   - `pnpm launch:beta:readiness` for the controlled beta go/no-go summary.\n   - `pnpm launch:review:production` for fail-fast production review.\n11. After every domain change, redeploy and refresh the homepage in Facebook\n   Sharing Debugger plus the equivalent LinkedIn/X preview tools before\n   circulating public links.\n\n## Pass Criteria\n\n- Staging can proceed with warnings only when the warnings are explicitly accepted for rehearsal.\n- Production must have `status: \"pass\"` in `deployment-preflight-latest.json`.\n- 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.\n- 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`.\n- Passing controlled beta readiness means staging can go live with only accepted evidence-collection warnings; production still requires `pnpm launch:review:production`.\n","sections":[{"level":2,"heading":"Commands","anchor":"commands"},{"level":2,"heading":"What It Checks","anchor":"what-it-checks"},{"level":2,"heading":"Required Environment Surface","anchor":"required-environment-surface"},{"level":2,"heading":"Launch Review Sequence","anchor":"launch-review-sequence"},{"level":2,"heading":"Pass Criteria","anchor":"pass-criteria"}],"html":"<h1 id=\"deployment-preflight-runbook\">Deployment Preflight Runbook</h1>\n<p>This runbook turns the roadmap&#39;s deployment-foundation work into a repeatable launch-readiness check. It does <strong>not</strong> replace the Era C exit gate; it verifies that the deployed environment is ready to start collecting trustworthy staging or production evidence.</p>\n<h2 id=\"commands\">Commands</h2>\n<p>Controlled public beta / staging rehearsal:</p>\n<pre><code>\npnpm launch:preflight\n</code></pre>\n<p>Production launch review:</p>\n<pre><code>\npnpm launch:preflight:production\n</code></pre>\n<p>One-command launch evidence refresh:</p>\n<pre><code>\npnpm launch:evidence -- --base-url=https://&lt;deploy-host&gt; --iiif-tile-url=https://&lt;iiif-tile&gt;\npnpm launch:evidence:production -- --base-url=https://&lt;shareable-url&gt; --public-read-base-url=https://&lt;shareable-url&gt; --iiif-tile-url=https://&lt;iiif-tile&gt; --validation-service-url=https://metamuseum-validation.onrender.com/validate --reconciliation-service-url=https://metamuseum-reconciliation.onrender.com\n</code></pre>\n<p>Artifacts:</p>\n<ul><li>`artifacts/launch/deployment-preflight-latest.json`</li><li>`artifacts/launch/runs/deployment-preflight-&lt;timestamp&gt;.json`</li></ul>\n<p>For production-target runs, localhost/private deployment targets are treated as</p>\n<p>operator diagnostics. The command still writes the timestamped run artifact so</p>\n<p>the failure can be inspected, but it does not replace</p>\n<p>`deployment-preflight-latest.json` when production URL checks show `localhost`,</p>\n<p>`127.0.0.1`, or `::1`.</p>\n<p>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.</p>\n<p>For production evidence runs from a workstation that also has staging-only</p>\n<p>values such as `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN`, set</p>\n<p>`METAMUSEUM_SKIP_ENV_FILES=1` and provide the required production variables</p>\n<p>explicitly so local `.env*` files cannot pollute the preflight artifact.</p>\n<h2 id=\"what-it-checks\">What It Checks</h2>\n<p>  the configured public base URL, so auth readiness is live-route evidence</p>\n<p>  rather than only secret presence.</p>\n<p>  `METAMUSEUM_SECRET_ROTATION_CONFIRMED_AT` must be current, and</p>\n<p>  `METAMUSEUM_SECRET_ROTATION_KEYS` must name the launch-critical keys rotated</p>\n<p>  after any exposure.</p>\n<p>  checked `.env*` paths, tracked sensitive filenames, and matching commit hashes;</p>\n<p>  production fails if sensitive env files are tracked, still appear in history,</p>\n<p>  or the git probe cannot run.</p>\n<p>  `METAMUSEUM_PUBLIC_READ_BASE_URL` set to the same public URL, so absolute</p>\n<p>  Open Graph image URLs do not point crawlers at a protected preview host.</p>\n<p>  Equal localhost or non-HTTPS values are still production failures because</p>\n<p>  crawler debug tools cannot fetch private origins.</p>\n<p>  preflight unless `CRON_SECRET` and `METAMUSEUM_OUTBOX_CRON_ENABLED=1` are set,</p>\n<p>  and live MediaWiki/Wikibase publication endpoints or bot tokens fail unless</p>\n<p>  `CRON_SECRET` and `METAMUSEUM_PUBLISH_QUEUE_CRON_ENABLED=1` are set.</p>\n<p>  `pnpm projection:readiness` records record count, discovery-workflow count,</p>\n<p>  graph-workflow count, and search/graph p95 metrics as `portable`, `watch`, or</p>\n<p>  `enable` / `enabled` evidence.</p>\n<p>  the local fallback/in-process behavior, while configured URLs must have a</p>\n<p>  `/health` probe with status and duration so cold starts and timeouts appear</p>\n<p>  in launch evidence. The long external-service probe timeout is only evidence</p>\n<p>  capture; production strict `/api/validate` is closed by default unless</p>\n<p>  `METAMUSEUM_STRICT_VALIDATION_PUBLIC=1` is set. A public Render-hosted strict</p>\n<p>  validator also requires paid/no-sleep capacity and a passing user-facing</p>\n<p>  health budget.</p>\n<p>  production probe-based uptime must target the final public HTTPS deployment</p>\n<p>  URL, not localhost or another private/non-HTTPS origin.</p>\n<ul><li>Storage resolves to Postgres-backed mode via `DATABASE_URL` and `METAMUSEUM_STORAGE_MODE`.</li><li>Database SSL mode is pinned to `sslmode=verify-full`; staging warns on `require`/missing values, and production fails until full verification is explicit.</li><li>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.</li><li>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.</li><li>Auth.js launch secrets are present and not using the development fallback.</li><li>GitHub OAuth credentials are present for sign-in; failures name the exact missing variable without exposing secret values.</li><li>Auth.js deployed routes answer `/api/auth/signin` and `/api/auth/session` on</li><li>Secret rotation evidence is recorded without secret values:</li><li>Sensitive env-file history is checked from git: preflight records only the</li><li>Public base URL is configured for smoke, uptime, and SLO commands.</li><li>Production social-preview metadata has both `BASE_URL` and</li><li>Worker scheduler readiness is explicit: projection targets fail production</li><li>Projection scale readiness is explicit before target flags are enabled:</li><li>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.</li><li>Optional Render validation/reconciliation mode is explicit: unset URLs record</li><li>Public-read uptime source is configured through Prometheus or scheduled probes;</li><li>k6 has a real deployed target and an explicit, fetchable `IIIF_TILE_URL` for launch SLO evidence.</li><li>Latest DR drill artifact is fresh and has a verified Postgres restore rehearsal.</li><li>Test-role override secrets are available for staging smoke, pass a basic secret-quality check, and are absent from production.</li></ul>\n<h2 id=\"required-environment-surface\">Required Environment Surface</h2>\n<p>Minimum public-beta/prod variables:</p>\n<p>  shell/CI variables instead of local `.env*` files.</p>\n<p>  latest exposed-secret rotation.</p>\n<p>  `AUTH_SECRET`, `AUTH_GITHUB_SECRET`, `DATABASE_URL`, `ANTHROPIC_API_KEY`,</p>\n<p>  `SMITHSONIAN_API_KEY`, and `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN` after those</p>\n<p>  values have been rotated in local, CI, and deployment secret stores.</p>\n<p>  `OUTBOX_PROJECT_TO_GRAPHDB=1`.</p>\n<p>  `METAMUSEUM_MEDIAWIKI_<em>` or `METAMUSEUM_WIKIBASE_</em>` endpoints/tokens are set.</p>\n<ul><li>`DATABASE_URL` with `sslmode=verify-full`</li><li>`METAMUSEUM_STORAGE_MODE=postgres`</li><li>`METAMUSEUM_SKIP_ENV_FILES=1` when running production preflight from explicit</li><li>`AUTH_SECRET`</li><li>`AUTH_GITHUB_ID`</li><li>`AUTH_GITHUB_SECRET`</li><li>`METAMUSEUM_SECRET_ROTATION_CONFIRMED_AT` as an ISO timestamp or date for the</li><li>`METAMUSEUM_SECRET_ROTATION_KEYS` as a comma-separated list including</li><li>`BASE_URL`</li><li>`METAMUSEUM_PUBLIC_READ_BASE_URL` set to the same public URL as `BASE_URL`</li><li>`METAMUSEUM_UPTIME_PROMETHEUS_URL` or scheduled probe config via `METAMUSEUM_PUBLIC_READ_BASE_URL` set to the final public HTTPS deployment URL</li><li>`IIIF_TILE_URL`</li><li>`CRON_SECRET` when projection or live publication scale flags are enabled.</li><li>`METAMUSEUM_OUTBOX_CRON_ENABLED=1` before `OUTBOX_PROJECT_TO_SOLR=1` or</li><li>`METAMUSEUM_PUBLISH_QUEUE_CRON_ENABLED=1` before live</li></ul>\n<p>Optional external service evidence:</p>\n<p>  intentionally public. Leave unset/off for operator-only validation; production</p>\n<p>  route handling returns `404` by default.</p>\n<p>  cold starts need a longer readiness window.</p>\n<p>  validation service off sleeping/free-tier capacity before exposing strict</p>\n<p>  `/api/validate` to production users.</p>\n<p>  user-facing strict-validation health budget. This stays separate from</p>\n<p>  `METAMUSEUM_EXTERNAL_SERVICE_PROBE_TIMEOUT_MS`, which may remain high enough</p>\n<p>  to capture cold-start evidence.</p>\n<p>  calls; strict validation returns `502` instead of waiting indefinitely.</p>\n<ul><li>`VALIDATION_SERVICE_URL=https://metamuseum-validation.onrender.com/validate` for the Render validation service `/validate` URL.</li><li>`METAMUSEUM_STRICT_VALIDATION_PUBLIC=1` only when strict `/api/validate` is</li><li>`RECONCILIATION_SERVICE_URL=https://metamuseum-reconciliation.onrender.com` for the Render reconciliation service base URL.</li><li>`METAMUSEUM_EXTERNAL_SERVICE_PROBE_TIMEOUT_MS` (default `45000`) when Render</li><li>`METAMUSEUM_VALIDATION_SERVICE_CAPACITY=paid` after upgrading the Render</li><li>`METAMUSEUM_VALIDATION_SERVICE_MAX_HEALTH_MS` (default `5000`) for the</li><li>`VALIDATION_TIMEOUT_MS` (default `15000`) for runtime `/api/validate` proxy</li></ul>\n<p>When `VALIDATION_SERVICE_URL` is unset, non-strict import/inspect flows use the</p>\n<p>local Linked Art profile fallback after the localhost validation default is</p>\n<p>unavailable; strict `/api/validate` still requires a running validation service</p>\n<p>and is closed in production unless `METAMUSEUM_STRICT_VALIDATION_PUBLIC=1`.</p>\n<p>When `RECONCILIATION_SERVICE_URL` is unset, the app uses its in-process</p>\n<p>deterministic reconciliation and optional LLM tiebreaker.</p>\n<p>Staging-only smoke support:</p>\n<ul><li>`METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN` (generated by `pnpm launch:smoke-token`; same value on server and smoke runner)</li></ul>\n<h2 id=\"launch-review-sequence\">Launch Review Sequence</h2>\n<ol><li>Configure the deployed environment and secrets.</li></ol>\n<ol><li>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.</li></ol>\n<ol><li>Run `pnpm storage:export:postgres` after setting `DATABASE_URL`; the exporter uses the same managed-document contract as `src/utils/storage.ts`.</li></ol>\n<ol><li>Run `pnpm dr:drill` against the configured storage mode.</li></ol>\n<ol><li>Before enabling Solr/GraphDB projection flags, run `pnpm projection:readiness -- --active-discovery-workflows=&lt;count&gt; --active-graph-workflows=&lt;count&gt; --search-p95-ms=&lt;ms&gt; --graph-p95-ms=&lt;ms&gt;`.</li></ol>\n<ol><li>Before production preflight, finish the credential-rotation runbook and keep</li></ol>\n<p>   the non-secret rotation stamp/key list in the deployment env. Then run</p>\n<p>   `pnpm launch:preflight` or `pnpm launch:preflight:production`; the command</p>\n<p>   probes the configured public URL to verify `/`, `/api/health`,</p>\n<p>   `/api/records`, `/api/auth/signin`, and `/api/auth/session` are reachable</p>\n<p>   without Vercel Authentication, fetches the configured `IIIF_TILE_URL`, probes configured</p>\n<p>   Render validation/reconciliation services at `/health`, checks sensitive</p>\n<p>   `.env*` tracking/history via git without printing secret values, captures</p>\n<p>   active Vercel deployment id/state/createdAt metadata, and, for production,</p>\n<p>   fails if `BASE_URL` and `METAMUSEUM_PUBLIC_READ_BASE_URL` are not the same</p>\n<p>   shareable public HTTPS URL or the active deployment predates the latest</p>\n<p>   launch-critical secret rotation. If `VALIDATION_SERVICE_URL` points at</p>\n<p>   Render for production, the same preflight also fails sleeping/free-tier</p>\n<p>   validation capacity or `/health` durations above the user-facing strict</p>\n<p>   validation budget even when the long cold-start probe eventually passes.</p>\n<ol><li>Prefer the evidence runner when refreshing the full packet:</li></ol>\n<ol><li>Or run public smoke checks manually:</li></ol>\n<ol><li>Collect performance and exit-gate evidence if not using the runner:</li></ol>\n<ol><li>Generate the launch decision packet if not using the runner:</li></ol>\n<ol><li>After every domain change, redeploy and refresh the homepage in Facebook</li></ol>\n<p>   Sharing Debugger plus the equivalent LinkedIn/X preview tools before</p>\n<p>   circulating public links.</p>\n<ul><li>`pnpm launch:evidence -- --base-url=https://&lt;deploy-host&gt; --iiif-tile-url=https://&lt;iiif-tile&gt;`</li><li>`pnpm launch:evidence:production -- --base-url=https://&lt;shareable-url&gt; --public-read-base-url=https://&lt;shareable-url&gt; --iiif-tile-url=https://&lt;iiif-tile&gt; --validation-service-url=https://metamuseum-validation.onrender.com/validate --reconciliation-service-url=https://metamuseum-reconciliation.onrender.com`</li><li>The runner writes `artifacts/launch/launch-evidence-latest.json`, keeps going after individual command failures, and records which evidence steps still block launch.</li><li>`BASE_URL=https://&lt;deploy-host&gt; pnpm smoke:public-trust`</li><li>`BASE_URL=https://&lt;deploy-host&gt; 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.</li><li>`BASE_URL=https://&lt;deploy-host&gt; pnpm a11y:check` writes `artifacts/launch/a11y-latest.json`.</li><li>`BASE_URL=https://&lt;deploy-host&gt; pnpm smoke:explore:matrix` writes `artifacts/launch/explore-smoke-latest.json` for staging researcher flow.</li><li>`BASE_URL=https://&lt;deploy-host&gt; IIIF_TILE_URL=https://iiif.micr.io/RFwqO/full/max/0/default.jpg pnpm k6:slo`</li><li>`pnpm era-c:exit-gate:evidence`</li><li>`pnpm launch:review` for controlled beta / staging.</li><li>`pnpm launch:beta:readiness` for the controlled beta go/no-go summary.</li><li>`pnpm launch:review:production` for fail-fast production review.</li></ul>\n<h2 id=\"pass-criteria\">Pass Criteria</h2>\n<ul><li>Staging can proceed with warnings only when the warnings are explicitly accepted for rehearsal.</li><li>Production must have `status: &quot;pass&quot;` in `deployment-preflight-latest.json`.</li><li>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.</li><li>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`.</li><li>Passing controlled beta readiness means staging can go live with only accepted evidence-collection warnings; production still requires `pnpm launch:review:production`.</li></ul>","updatedAt":"2018-10-20T01:46:40.000Z","checksum":"97db694dd88b710f4fd3cfc459ddd662911ff5cfb82b27cd2cceaddbf5ee6873","checksumPrefix":"97db694dd88b","anchorCount":5,"lineCount":198,"rawUrl":"/api/docs/content?path=ops%2Fdeployment-preflight.md","htmlUrl":"/docs?doc=ops%2Fdeployment-preflight.md","apiUrl":"/api/docs/content?path=ops%2Fdeployment-preflight.md"}