Skip to article
PlanToCodeDocsGet the app

HandbookContribute

Server, database roles, and operation

The account and relay service, the PostgreSQL role invariant, the configuration a development server needs, and what to verify beyond HTTP health.

Checked against the source on 17 September 2026

On this page

The server has account and relay responsibilities

The Actix application initializes shared services at startup: database pools, account authentication, connection management, and configured provider and push integrations. The DeviceConnectionManager is shared across HTTP workers in the process. Its live sockets, connection generations, and pending RPC records are process state, and the relay session store behind resume tokens is in memory too, with a 24-hour TTL and a 5-minute sweep. Redis is required for shared sign-in state, rate limiting, and credit reservations for paid requests, and the process exits at startup without it. It does not make a live socket registry portable between server processes.

Both devices and their pending request must reach that connection manager, so production runs one relay-serving process per region with explicit connection draining; the device routing chapter explains why a round-robin replica would break routing.

PostgreSQL roles are a startup invariant

The server exits at startup unless its two database logins are separate and confined by row-level security
The server exits at startup unless its two database logins are separate and confined by row-level securityInside PostgreSQL the migration role owns the tables, and 27 of them must have row-level security enabled and forced. The server connects through two logins, the system login plantocode and the tenant login authenticated. At startup it checks that neither login is a superuser, bypasses row-level security, inherits roles, or owns tables, that neither is the other or a member of it or of the table owner, and that the tenant login reads no rows from 16 tenant tables without a user ID. Any failure stops the process before it listens. Each server process opens its own system pool of 8 and tenant pool of 16 connections, shared by its HTTP workers.
PostgreSQL
Table ownerplantocode_migrator · SUPERUSER
27 tablesrow-level security enabled and forced
System loginplantocode
Tenant loginauthenticated
member of the owner
same login, or member of the other
reads 0 rows from 16 tables without a user ID
both: NOSUPERUSER · NOBYPASSRLS · NOINHERIT · own no tables
Each server process
system pool 8
tenant pool 16
shared by its HTTP workers
Any crossed line, a table without FORCE, or a visible row: create_dual_pools fails and the process exits before it listens.
Production also opens ACCOUNT_CONTROL_DATABASE_URL, with its own least-privilege check, and METRICS_DATABASE_URL.
  • Runtime loginsDATABASE_SYSTEM_URL · DATABASE_USER_URL

    assert_runtime_boundary checks the principals these URLs connect as, not the URLs. Before connecting, create_dual_pools also rejects two identical URLs.

  • Tenant contextapp.current_user_id

    Set with set_config for one transaction, so a pooled connection forgets it when the transaction ends. Row-level security policies written for each login decide what it may touch.

  • PoolsDB_POOL_SYSTEM_MAX · DB_POOL_USER_MAX

    Opened once per server process and shared by all its HTTP workers. Each extra process, including the second color during a blue-green cutover, opens its own.

create_dual_pools opens both pools and runs assert_runtime_boundary on the principals they actually connected as, so a URL that names the wrong role fails the same checks.

The pools default to 8 system and 16 tenant connections with minimums of 2 and 4, a 2-second acquire timeout, 60 seconds idle and 1,800 seconds maximum lifetime. Every connection sets UTC, an application_name of plantocode-system or plantocode-user, and statement, idle-in-transaction, and lock timeouts of 30, 60, and 5 seconds for the system role and 8, 10, and 2 seconds for the tenant role; connects retry five times with a linear one-second backoff. Each server process opens its own pools, shared by its HTTP workers, so more processes, including a blue-green cutover, multiply connections and need a database capacity check first.

Configure a local server

  1. Copy server/.env.example to server/.env and configure a local database, Redis URL, JWT and refresh-token encryption settings, and Auth0 values.
  2. Create separate migration and runtime principals against a local development database.
  3. Apply server/scripts/run-migrations.sh with the required migration environment; server/README.md describes role provisioning and the preservation policy.
Read and run from the repository root
cp server/.env.example server/.env
# Configure the local services and database roles first.
pnpm server:check
pnpm dev:server

The migration runner consumes infrastructure/ansible/playbooks/plantocode/migration-execution-plan.yml, the same ordered plan deployment uses. The migration role must be a superuser that is none of postgres, plantocode, or authenticated and is absent from DATABASE_RUNTIME_ROLES. Files marked execution once are sha256-locked in plantocode_internal.migration_execution_ledger, which runtime roles cannot read; a checksum mismatch, a multi-transaction once file, or an unplanned .sql file fails closed, and PRESERVE_EXISTING_DATABASE_IDENTITIES=true is accepted only for the expand phase. Do not replace it with sqlx migrate run.

Configuration familyRequired for
DATABASE_SYSTEM_URL, DATABASE_USER_URL, REDIS_URLCore database and shared sign-in services.
AUTH0_DOMAIN, AUTH0_API_AUDIENCE, AUTH0_ALLOWED_CLIENT_IDSThe configured account login trust boundary.
JWT and refresh-token encryption settings in .env.exampleIssuing app credentials and storing refresh material.
Provider API keysOnly the corresponding configured provider routes.
Stripe, APNs, FCM, mail and other integration settingsTheir respective account or notification features.
METRICS_DATABASE_URL, ACCOUNT_CONTROL_DATABASE_URLOptional, but each pulls in companions: SERVICE_HEALTH_REGION and SERVICE_HEALTH_RELEASE, or WORKSPACE_PREVIEW_HMAC_SECRET.
PLANTOCODE_DEPLOYMENT_TOKENThe protected /health/deployment endpoint and its drain action.

Verify behavior beyond HTTP health

GET /health checks basic server health. Protected /health/deployment supports deployment inspection. Then verify an account login, two device registrations, one read-only desktop RPC, and one controlled outbox operation. A healthy HTTP listener alone does not test database role separation, a native callback, provider configuration, or relay routing.

A useful relay trace follows the request ID, source client, target desktop, method, and connection generation. The forwarding code extracts W3C trace context and injects the child context into the next hop. For ambiguous delivery, inspect the desktop’s remote_rpc_idempotency claim and its codex_chat_operation_ledger rows before inferring whether work ran.

Production uses the repository’s Ansible blue-green workflow with separate database expansion, binary activation, verification, and contract phases; its inventory and secrets are operator configuration.