Core database boundary

April 28, 2026 · View on GitHub

Audience: engineer, operator.

Purpose

Prevent non-production-like processes (local dev, CI, preview, staging) from opening the production core Postgres DATABASE_URL. Sanctioned migration tooling must not bypass the check.

Production-like means VERCEL_ENV === "production" (see website/src/lib/canonicalSiteOrigin.ts).

Forbidden fingerprint (single source of truth)

  • File: config/commercial-production-core-database-fingerprint.sha256 — exactly one line: lowercase hex SHA-256 (64 chars).
  • Meaning: SHA-256 of the normalized connection string for the real production core database URL (see normalization below).
  • Rotation: When the production core DSN changes, update the file in a PR and redeploy; the hash must match computeCoreDatabaseFingerprint(normalizeDatabaseUrlForFingerprint(actualProductionUrl)).

Placeholder fingerprint (repo default): The committed file ships with a hash of a non-resolvable placeholder host (__agentskeptic_production_core__) so typical CI localhost URLs never match. Operators must replace this line with the real production fingerprint before relying on the boundary in production (or keep placeholder until production URL is finalized).

Normalization (normative)

Must match website/src/lib/coreDatabaseBoundary.ts and scripts/core-database-boundary-preflight.mjs:

  1. Trim the raw URL string.
  2. Replace postgres:// or postgresql:// with http:// for parsing only.
  3. Parse with URL; hostname lowercased; default port 5432 if absent.
  4. Query string: sort parameter keys lexicographically; rebuild as k=v joined with & (omit ? if empty).
  5. Canonical string: postgresql://{host}:{port}{pathname}{?sortedQuery} (credentials are not included — only host, port, path, sorted query).

computeCoreDatabaseFingerprint(url) = SHA-256 hex (lowercase) of the UTF-8 bytes of that canonical string.

Policy

  • If production-like → boundary check skipped for DATABASE_URL (production uses real hosted URLs).
  • If not production-like and DATABASE_URL is empty or equals the website build placeholder DSN (postgresql://127.0.0.1:5432/workflow_verifier_build_placeholder) → skipped.
  • Otherwise → if fingerprint equals the forbidden line → throw / exit 1 with message AGENTSKEPTIC_CORE_DATABASE_BOUNDARY_VIOLATION (see code for exact string).

Enforcement entrypoints (closed list)

LocationRole
website/src/db/client.tsCalls assertCoreDatabaseBoundary before postgres().
website/instrumentation.tsSame assert on cold start (belt-and-suspenders).
website/scripts/db-migrate.mjsRuns scripts/core-database-boundary-preflight.mjs after merging website/.env.
website/scripts/drizzle-kit-guarded.mjsPreflight then forwards to drizzle-kit.
scripts/validate-commercial-funnel.mjsPreflight after DATABASE_URL required check.
scripts/website-holistic-gate.mjsPreflight after env validation.
scripts/run-commercial-e2e.mjsPreflight; migrate via website/scripts/db-migrate.mjs (not raw drizzle-kit).
website/__tests__/helpers/siteTestServer.tsPreflight before starting Next.

Sanctioned drizzle-kit

Only:

Direct npx drizzle-kit … against team infrastructure is unsupported and not part of the compliance surface.

CI workflow host audit

  • Static scan: scripts/assert-ci-workflows-database-url-hosts.mjs — fails if any workflow literal DATABASE_URL: or TELEMETRY_DATABASE_URL: (non–${{ }}) uses a host other than localhost / 127.0.0.1.
  • Placement: .github/workflows/ci.ymljobs.verification → immediately after actions/checkout@v5 (before setup-node-npm).
  • Runtime guard (GitHub Actions only): scripts/assert-ci-postgres-env-safety.mjs with --require-core-and-telemetry on jobs.verification so the job must set both core and telemetry URLs and they must still resolve to localhost-only hosts (belt-and-suspenders vs secrets or env injection).
  • Telemetry writes: the job sets AGENTSKEPTIC_TELEMETRY_WRITES_TELEMETRY_DB=0 at the job level; scripts/verification-truth.mjs temporarily sets 1 only for the validate-commercial / related website steps. Fixture DB names wfv_website / wfv_telemetry on the job Postgres service match the split-store production pattern.