Environment Variables
This file documents the active Meta Museum environment-variable surface.
Core app
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `PORT` | No | `3000` | App listen port in local/dev or production process runners. |
| `NODE_ENV` | No | runtime-set | Standard Node mode (`development`, `test`, `production`). |
Storage and database
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `DATABASE_URL` | Required for Postgres mode | none | Postgres connection string for JSONB storage-of-record mode; launch preflight expects `sslmode=verify-full` for Neon/Postgres environments. |
| `METAMUSEUM_STORAGE_MODE` | No | auto (`postgres` if `DATABASE_URL` present; else `file`) | Storage mode selector: `file`, `double-write`, `postgres`. |
GitHub issues/SSE
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `GITHUB_OWNER` | No | repo default | Repository owner for issues inventory fetches. |
| `GITHUB_REPO` | No | repo default | Repository name for issues inventory fetches. |
| `ISSUE_POLL_MS` | No | service default | Poll cadence for issue refresh behavior. |
| `GITHUB_TOKEN` | No | none | Optional authenticated GitHub API access for higher rate limits. |
Auth.js / GitHub OAuth
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `AUTH_SECRET` | Yes in production | dev fallback (`metamuseum-dev-auth-secret-change-me`) when unset outside production | Auth.js secret for session/JWT security. |
| `AUTH_GITHUB_ID` | Yes for GitHub sign-in | none | GitHub OAuth app client ID. |
| `AUTH_GITHUB_SECRET` | Yes for GitHub sign-in | none | GitHub OAuth app client secret. |
| `AUTH_TRUST_HOST` | Optional | unset | Host trust override for Auth.js in certain deployments. |
Secret rotation evidence
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `METAMUSEUM_SECRET_ROTATION_CONFIRMED_AT` | Production launch preflight yes | none | Non-secret ISO timestamp/date for the latest exposed-secret rotation. |
| `METAMUSEUM_SECRET_ROTATION_KEYS` | Production launch preflight yes | none | Comma-separated key names rotated after exposure; production preflight expects `AUTH_SECRET`, `AUTH_GITHUB_SECRET`, `DATABASE_URL`, `ANTHROPIC_API_KEY`, `SMITHSONIAN_API_KEY`, and `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN`. |
Roles and allowlists
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `MM_RESEARCHER_ALLOWLIST` | No | empty | Comma-separated identities granted `researcher`. |
| `MM_EDITOR_ALLOWLIST` | No | empty | Comma-separated identities granted `editor`. |
| `MM_ADMIN_ALLOWLIST` | No | empty | Comma-separated identities granted `admin`. |
Optional provider keys (as providers require)
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `SMITHSONIAN_API_KEY` | Required for Smithsonian live API mode | none | Smithsonian Open Access API key. |
| `HARVARD_API_KEY` | Required for Harvard live API mode | none | Harvard Art Museums API key. |
| `RKD_TRIPLY_TOKEN` | Optional/depends on endpoint policy | none | RKD Triply API bearer token. |
Validation service
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `VALIDATION_SERVICE_URL` | Launch readiness yes | local default | URL for FastAPI validation microservice proxy (`/api/validate`); production readiness uses `https://metamuseum-validation.onrender.com/validate`. |
| `VALIDATION_TIMEOUT_MS` | No | `15000` | Runtime timeout for strict and non-strict validation proxy requests; strict `/api/validate` returns `502` when this budget is exceeded. |
| `METAMUSEUM_STRICT_VALIDATION_PUBLIC` | No | off in production | Set to `1`/`true`/`yes`/`on` only when strict `/api/validate` is intentionally public. Production route handling returns `404` by default, and deployment preflight treats Render validation as operator-only unless this flag is enabled. |
| `METAMUSEUM_VALIDATION_SERVICE_CAPACITY` | Production when using Render validation | none | Non-secret capacity declaration for user-facing strict validation. Set to `paid`, `reserved`, `dedicated`, or another accepted no-sleep value after upgrading the Render validation service; production preflight fails an `onrender.com` strict validator without it. |
| `METAMUSEUM_VALIDATION_SERVICE_MAX_HEALTH_MS` | No | `5000` | User-facing strict-validation health budget checked by deployment preflight separately from the longer cold-start probe timeout. |
Reconciliation service
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `RECONCILIATION_SERVICE_URL` | Launch readiness yes | unset | Deployed FastAPI reconciliation service base URL; production readiness uses `https://metamuseum-reconciliation.onrender.com`. When unset, the app uses in-process deterministic reconciliation plus the optional LLM tiebreaker. |
| `METAMUSEUM_RECONCILIATION_SERVICE_URL` | No | unset | Legacy/alternate name accepted by deployment preflight for the same deployed reconciliation service base URL. |
| `RECONCILIATION_REDIS_URL` | Service only | `redis://127.0.0.1:6379/0` | Redis cache URL for the FastAPI reconciliation service. |
| `RECONCILIATION_HTTP_TIMEOUT_SECONDS` | Service only | `15` | Per-provider HTTP timeout inside the FastAPI reconciliation service. |
AG2 worker and bridge
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `METAMUSEUM_AG2_BRIDGE_ENABLED` | No | `0` | Enables the internal AG2 bridge for eligible review agents only; keep disabled in production unless the AG2 worker runbook evidence is current. |
| `METAMUSEUM_AG2_BRIDGE_URL` | Required only when bridge is enabled | none | Internal AG2 worker endpoint, usually `http://127.0.0.1:7788/ag2/agents/run`. |
| `METAMUSEUM_AG2_BRIDGE_TIMEOUT_MS` | No | `5000` | Bridge request timeout, clamped by code; timeout returns local review fallback. |
| `METAMUSEUM_AG2_WORKER_HOST` | No | `127.0.0.1` | Bind host for `services/ag2-worker`; keep loopback or private. |
| `METAMUSEUM_AG2_WORKER_PORT` | No | `7788` | Bind port for the local AG2 worker. |
| `METAMUSEUM_AG2_WORKER_MAX_RECORDS` | No | `50` | Maximum records accepted per worker request. |
| `METAMUSEUM_AG2_WORKER_MAX_COLUMNS` | No | `100` | Maximum mapping columns accepted per worker request. |
| `METAMUSEUM_AG2_EVAL_TARGET` | No | `local` | Target label for `pnpm ag2:worker:eval`; production requires sign-off. |
| `METAMUSEUM_AG2_OPERATOR_NAME` | Production eval only | none | Operator identity recorded in AG2 worker eval artifacts. |
| `METAMUSEUM_AG2_OPERATOR_SIGNED_AT` | Production eval only | none | ISO timestamp for operator sign-off. |
| `METAMUSEUM_AG2_OPERATOR_SCOPE` | Production eval only | none | Scope/rationale for AG2 bridge enablement sign-off. |
Launch readiness / observability
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `BASE_URL` | Required for launch smoke/SLO commands and production social previews | `http://localhost:3000` in local scripts | Deployed app URL for smoke, a11y, k6 SLO commands, and absolute metadata. In production it must match `METAMUSEUM_PUBLIC_READ_BASE_URL`. |
| `METAMUSEUM_PUBLIC_READ_BASE_URL` | Required in production and when using probe uptime source | falls back to `BASE_URL` for telemetry sync | Public-read probe and share-preview base URL. In production it must be the same public HTTPS URL as `BASE_URL`, because Open Graph image URLs are resolved for external crawlers against this base. |
| `METAMUSEUM_UPTIME_PROMETHEUS_URL` | Required when using Prometheus uptime source | none | Prometheus base URL for 30-day public-read uptime queries. |
| `METAMUSEUM_UPTIME_PROMQL_AVAILABILITY` | No | `avg_over_time(probe_success{job="metamuseum-public-read"}[30d])` | PromQL expression for 30-day availability. |
| `METAMUSEUM_UPTIME_PROMQL_SAMPLE_COUNT` | No | `count_over_time(probe_success{job="metamuseum-public-read"}[30d])` | PromQL expression for 30-day sample count. |
| `METAMUSEUM_UPTIME_PROBE_PATHS` | No | `/`, `/api/health`, `/api/activity` | Comma-separated paths for scheduled probe uptime evidence; probe history records per-path URL, status, duration, and error details. |
| `METAMUSEUM_UPTIME_PROBE_TIMEOUT_MS` | No | `5000` | Timeout for public-read probe requests. |
| `IIIF_TILE_URL` | Required for launch SLO evidence | local icon fallback in k6 | CDN-backed IIIF tile URL for the SOTA §20.4 tile p95 check. |
| `METAMUSEUM_EVIDENCE_BASE_URL` | Required for deployed-target nightly evidence | none | GitHub Actions variable copied to `BASE_URL` for nightly k6, AI-query telemetry seeding, and adoption proof. |
| `METAMUSEUM_EVIDENCE_IIIF_TILE_URL` | Required for deployed-target nightly SLO evidence | none | GitHub Actions variable copied to `IIIF_TILE_URL` for the nightly IIIF tile p95 check. |
| `METAMUSEUM_EVIDENCE_SPARQL_URL` | Required for deployed-target SPARQL SLO evidence | none | GitHub Actions variable copied to `SPARQL_URL` for the nightly whitelisted SPARQL p95 check. |
| `METAMUSEUM_EVIDENCE_SPARQL_QUERY` | No | k6 script default | Optional GitHub Actions query override copied to `SPARQL_QUERY` for the nightly SPARQL p95 check. |
| `METAMUSEUM_EVIDENCE_AI_QUERY` | No | `find art records` | Representative deployed `/api/ai/query` prompt used by the nightly evidence workflow before telemetry sync. |
| `METAMUSEUM_KPI_EVIDENCE_PATH` | No | `monitoring/kpi-evidence.json` | Optional aggregate SOTA §26 KPI evidence file for record-enrichment and reconciliation metrics. |
| `METAMUSEUM_DEPLOYMENT_TARGET` | No | `staging` | Default target for `pnpm launch:preflight`; allowed values: `staging`, `production`. |
| `METAMUSEUM_SKIP_ENV_FILES` | No | unset | Set to `1`/`true`/`yes` for explicit production evidence runs that must not load local `.env*` files, such as `pnpm launch:preflight:production` when a local staging smoke token exists. |
| `METAMUSEUM_EXTERNAL_SERVICE_PROBE_TIMEOUT_MS` | No | `45000` | Timeout used by deployment preflight when probing configured validation/reconciliation Render `/health` endpoints. |
| `METAMUSEUM_A11Y_EVIDENCE_PATH` | No | `artifacts/launch/a11y-latest.json` | Output path for `pnpm a11y:check` launch-review evidence. |
| `METAMUSEUM_EXPLORE_SMOKE_EVIDENCE_PATH` | No | `artifacts/launch/explore-smoke-latest.json` | Output path for `pnpm smoke:explore:matrix` launch-review evidence. |
| `METAMUSEUM_TEST_ROLE_OVERRIDE_TOKEN` | Staging smoke only | none | Enables researcher-role smoke checks; generate/update local `.env` with `pnpm launch:smoke-token`, copy the same value to the staging server and smoke runner, and never set it for production. |
| `METAMUSEUM_ACTIVITY_CONSUMER_ID` | Required for `pnpm activity:adoption:probe` unless `--consumer-id` is passed | none | Partner-owned declared consumer ID sent as `x-linked-art-consumer-id` to prove `/api/activity` adoption. |
| `METAMUSEUM_ACTIVITY_CONSUMER_IDS` | Required for `pnpm activity:adoption:matrix` unless `--consumer-id`/`--consumer-ids` is passed | none | Comma-separated partner-owned declared consumer IDs used to prove the full three-consumer `/api/activity` adoption checkpoint. |
| `NEXT_PUBLIC_GA_MEASUREMENT_ID` | No | none | Public GA4 measurement ID for the website data stream. Set to `G-WPGJX5H0S7` for the current Meta Museum property/stream to load the Google tag once from the root layout. Consent Mode v2 defaults deny ad storage, ad user data, ad personalization, and analytics storage until the browser has a persisted analytics-consent choice; advertising consent remains denied even when analytics is accepted. |
Scheduled worker drains
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `CRON_SECRET` | Yes when a cron drain is enabled | none | Shared bearer token for `/api/cron/outbox` and `/api/cron/publish`; Vercel Cron sends `Authorization: Bearer $CRON_SECRET`. Disabled scheduled drains return `200` no-op JSON without this secret so idle schedules do not inflate Vercel error rate. |
| `METAMUSEUM_OUTBOX_CRON_ENABLED` | Required before projection targets are live | off | Set to `1` to let `/api/cron/outbox` drain one bounded outbox projector batch. Production preflight fails when `OUTBOX_PROJECT_TO_SOLR` or `OUTBOX_PROJECT_TO_GRAPHDB` is enabled without this flag and `CRON_SECRET`. |
| `METAMUSEUM_OUTBOX_CRON_LIMIT` | No | `25` | Maximum outbox events claimed per cron invocation, clamped by code. |
| `METAMUSEUM_OUTBOX_CRON_WORKER_ID` | No | generated per invocation | Optional stable worker id for outbox cron logs. |
| `OUTBOX_PROJECT_TO_SOLR` / `OUTBOX_PROJECT_TO_GRAPHDB` | Required only when projection targets are live | off | Projection target gates used by the outbox projector and cron drain. Set at least one to `1` only after Solr/GraphDB are provisioned and `/api/cron/outbox` is scheduled/enabled; projector calls skip and cron will not claim outbox events without an explicit target. |
| `METAMUSEUM_PUBLISH_QUEUE_CRON_ENABLED` | Required before live publication endpoints/tokens are configured | off | Set to `1` to let `/api/cron/publish` process ready publish queue items. Production preflight fails when live MediaWiki/Wikibase endpoints or bot tokens are configured without this flag and `CRON_SECRET`. |
| `METAMUSEUM_PUBLISH_QUEUE_CRON_MAX_ITEMS` | No | `1` | Maximum publish queue items processed per cron invocation, clamped by code. |
| `METAMUSEUM_PUBLISH_QUEUE_PATH` | No | `storage/publish-queue.json` | Publish queue document path; default is managed storage and Postgres-backed in `postgres` mode. |
| `METAMUSEUM_PUBLISH_SCHEDULE_PATH` | No | `distribution/schedule.yaml` | Distribution schedule used by the publish queue worker. |
Notes
- Do not commit secrets in `.env` files.
- Rotate any legacy OAuth credentials that were previously exposed.
- For production credential rotation (`AUTH_SECRET`, `AUTH_GITHUB_SECRET`), follow `docs/ops/auth-credential-rotation.md`(docs/ops/auth-credential-rotation.md).
- Runtime validation for key env vars is enforced in `src/utils/env.ts`.