Persistence and environments

MAIL-72 stands up the PostgreSQL migration runner and fail-closed environment validation. Domain tables are added by later tickets.

Host

ItemValue
ProjectSupabase Sandbox
Refhijznwisiirxdknbnvyz
Hostdb.hijznwisiirxdknbnvyz.supabase.co
Schemainbox_agent

This is a shared database. Never drop unrelated schemas or tables. See ADR 0002.

Required environment

Process start (loadRequiredEnv in @inbox-agent/config) refuses to boot unless all of the following are present and valid:

VariableRule
APP_ENVlocal, preview, production, or test
DATABASE_URLpostgres:// or postgresql:// URL
TOKEN_ENCRYPTION_KEYsecret, at least 32 characters
SESSION_SECRETsecret, at least 32 characters

Copy .env.example to a local .env that is never committed. Do not put secrets in Git, fixtures, docs, or Jira.

ENABLE_SEND and ENABLE_DELETE remain default-off feature flags (MAIL-70). They are not substitutes for the secrets above.

Migrations

SQL files live in database/migrations/ as NNNN_name.sql. The runner:

  1. Applies pending files in version order inside a transaction
  2. Records version, name, and SHA-256 checksum in inbox_agent.schema_migrations
  3. Refuses checksum drift for an already-applied version

Current head (MAIL-96): 0011_conversation_states.sql — conversation-level attention keyed by thread_id (ADR 0009). Sprint 02 added connected_accounts through conversation_states (00040011). MAIL-74 0003_encrypted_provider_tokens.sql remains ciphertext-only. MAIL-73 0002_users.sql and MAIL-72 0001_inbox_agent_bootstrap.sql are unchanged. See canonical schema.

# Real Postgres (local service, CI service, or Supabase). Fails closed without DATABASE_URL.
npm run db:migrate

# CI gate: DATABASE_URL when set, otherwise ephemeral PGlite.
npm run migrate:ci

migrate:ci is the hook reserved by MAIL-70. This ticket supplies the runner that applies SQL.

Queue (Sprint 01)

PostgreSQL transactional outbox only. Do not add Redis or SQS. The outbox table is a later migration.

RLS

inbox_agent.schema_migrations, inbox_agent.users, and inbox_agent.provider_tokens have row-level security enabled. anon and authenticated are revoked when those roles exist. Keep inbox_agent off the Supabase Data API until a later ticket defines an access model.

Provider token ciphertext is not readable from the database alone. Decrypt requires TOKEN_ENCRYPTION_KEY via TokenSecretStore. See provider token encryption and ADR 0004.