-- environments — the disposable per-student lab registry. -- See docs/LAB_ARCHITECTURE.md §3 and docs/PHASE1_PLATFORM_FOUNDATION.md §2. DO $$ BEGIN CREATE TYPE environment_status AS ENUM ( 'requested', 'provisioning', 'running', 'resetting', 'suspended', -- experimental path only, see decision #3 / correction A 'destroying', 'destroyed', 'error' ); EXCEPTION WHEN duplicate_object THEN NULL; END $$; CREATE TABLE IF NOT EXISTS environments ( environment_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), student_id TEXT NOT NULL, -- opaque external identifier (correction C) — -- no FK: students table isn't in this database -- until a later, separate Platform DB migration. -- The Provisioner trusts its caller on this value. lab_host_id TEXT NOT NULL REFERENCES lab_hosts(lab_host_id), -- real FK: both -- tables are Provisioner-owned Postgres. status environment_status NOT NULL DEFAULT 'requested', routing_token TEXT NOT NULL, -- CSPRNG, opaque, unique container_id TEXT, container_name TEXT, network_id TEXT, network_name TEXT, network_subnet CIDR, -- needed to install/remove the per-env firewall chain image_ref TEXT NOT NULL, cpu_limit NUMERIC NOT NULL, mem_limit_mb INTEGER NOT NULL, pids_limit INTEGER NOT NULL, storage_limit_mb INTEGER NOT NULL, internal_ip TEXT, internal_port INTEGER NOT NULL DEFAULT 80, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), started_at TIMESTAMPTZ, last_active_at TIMESTAMPTZ, destroy_at TIMESTAMPTZ, -- idle-reaper's scheduled destroy time error_reason TEXT ); CREATE UNIQUE INDEX IF NOT EXISTS idx_environments_routing_token ON environments(routing_token); CREATE INDEX IF NOT EXISTS idx_environments_student ON environments(student_id); CREATE INDEX IF NOT EXISTS idx_environments_status ON environments(status); CREATE INDEX IF NOT EXISTS idx_environments_lab_host ON environments(lab_host_id); -- Phase 1 invariant "at most one non-terminal environment per student" is enforced in -- application logic (lifecycle.js), not a DB constraint yet — a partial unique index is the -- natural upgrade once this needs to be bulletproof under concurrent requests, not needed for -- a single-instance provisioner in this phase.