-- T2 — control-plane schema. Transcribes INDEX §4.2 (accounts/hosts/sessions/pairing_codes) -- VERBATIM, plus P3-owned tables (relay_nodes, metering_samples, audit_log) and the INV8 companion -- status-version tables + current-pointer columns (OQ6 — pending INDEX promotion of §4.2). -- -- Discipline: no bare UPDATE of a lifecycle/business field without a matching *_status_versions -- insert in the SAME transaction. Liveness columns (last_seen, last_attach_at) are advisory -- caches (INV7 — live truth is Redis) and are exempt from versioning. CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- gen_random_uuid() -- ---- accounts (§4.2) -------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS accounts ( account_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- unguessable, never recycled plan text NOT NULL, -- 'free'|'personal'|'pro'|'team' created_at timestamptz NOT NULL DEFAULT now(), status text NOT NULL DEFAULT 'active', -- 'active'|'suspended' (current pointer) status_version int NOT NULL DEFAULT 1, -- INV8 monotonic pointer CONSTRAINT accounts_plan_chk CHECK (plan IN ('free','personal','pro','team')), CONSTRAINT accounts_status_chk CHECK (status IN ('active','suspended')) ); CREATE TABLE IF NOT EXISTS account_status_versions ( account_id uuid NOT NULL REFERENCES accounts(account_id), version int NOT NULL, status text NOT NULL, changed_at timestamptz NOT NULL DEFAULT now(), changed_by text NOT NULL, PRIMARY KEY (account_id, version) ); -- ---- hosts (§4.2) — ownership source of truth -------------------------------------------------- CREATE TABLE IF NOT EXISTS hosts ( host_id uuid PRIMARY KEY DEFAULT gen_random_uuid(), -- unguessable, NEVER recycled (INV1) account_id uuid NOT NULL REFERENCES accounts(account_id), subdomain text NOT NULL UNIQUE, -- stable across reconnects agent_pubkey bytea NOT NULL, -- Ed25519 PUBLIC key only (INV4) enroll_fpr text NOT NULL, -- fingerprint of agent_pubkey (E2E TOFU) status text NOT NULL DEFAULT 'offline', -- 'online'|'offline'|'draining'|'revoked' last_seen timestamptz NOT NULL DEFAULT now(), -- advisory cache (NOT versioned, INV7) created_at timestamptz NOT NULL DEFAULT now(), revoked_at timestamptz NULL, -- set on revocation (INV12) status_version int NOT NULL DEFAULT 1, CONSTRAINT hosts_status_chk CHECK (status IN ('online','offline','draining','revoked')) ); CREATE INDEX IF NOT EXISTS hosts_account_idx ON hosts(account_id); CREATE TABLE IF NOT EXISTS host_status_versions ( host_id uuid NOT NULL REFERENCES hosts(host_id), version int NOT NULL, status text NOT NULL, revoked_at timestamptz NULL, changed_at timestamptz NOT NULL DEFAULT now(), changed_by text NOT NULL, PRIMARY KEY (host_id, version) ); -- ---- sessions (§4.2) -------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS sessions ( session_id uuid PRIMARY KEY, -- base-app sessionId, opaque to relay host_id uuid NOT NULL REFERENCES hosts(host_id), account_id uuid NOT NULL, -- DENORMALIZED for O(1) authz (INV3/INV6) created_at timestamptz NOT NULL DEFAULT now(), last_attach_at timestamptz NOT NULL DEFAULT now() -- advisory cache (NOT versioned) ); -- ---- pairing_codes (§4.2/§4.5) ---------------------------------------------------------------- CREATE TABLE IF NOT EXISTS pairing_codes ( code_hash text PRIMARY KEY, -- hash of single-use code; raw NEVER stored (INV5) account_id uuid NOT NULL REFERENCES accounts(account_id), expires_at timestamptz NOT NULL, -- short TTL redeemed_at timestamptz NULL, -- single-use: non-null ⇒ spent redeem_attempts int NOT NULL DEFAULT 0 -- code-scoped brute-force lockout (T8) ); -- ---- relay_nodes (P3-owned) ------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS relay_nodes ( node_id text PRIMARY KEY, -- SPIFFE-style id from mTLS cert subject addr text NOT NULL, status text NOT NULL DEFAULT 'active', -- 'active'|'draining' last_seen timestamptz NOT NULL DEFAULT now(), CONSTRAINT relay_nodes_status_chk CHECK (status IN ('active','draining')) ); -- ---- metering_samples (P3-owned, append-only INV8) -------------------------------------------- CREATE TABLE IF NOT EXISTS metering_samples ( id bigserial PRIMARY KEY, host_id uuid NOT NULL REFERENCES hosts(host_id), account_id uuid NOT NULL, -- derived server-side from ownership (INV1) node_id text NOT NULL, -- authenticated mTLS identity (INV3-analog) concurrent_viewers int NOT NULL, sampled_at timestamptz NOT NULL ); CREATE INDEX IF NOT EXISTS metering_account_time_idx ON metering_samples(account_id, sampled_at); -- ---- audit_log (P3-owned, append-only zero-payload INV10) ------------------------------------- CREATE TABLE IF NOT EXISTS audit_log ( id bigserial PRIMARY KEY, action text NOT NULL, principal_id text NOT NULL, account_id text NOT NULL, host_id text NULL, ts timestamptz NOT NULL DEFAULT now(), meta jsonb NOT NULL DEFAULT '{}'::jsonb -- metadata only; NO terminal payload (INV10) ); CREATE INDEX IF NOT EXISTS audit_account_time_idx ON audit_log(account_id, ts);