-- Release module full schema bootstrap for local dev compose. -- Includes schemas, tenants, integration hub, environments, release management, -- workflow, promotion, deployment, agents, trust/signing, and read models. -- All statements are idempotent (IF NOT EXISTS / ON CONFLICT). -- Shared tenants (required by release.integrations FK) CREATE SCHEMA IF NOT EXISTS shared; CREATE TABLE IF NOT EXISTS shared.tenants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL UNIQUE, name TEXT NOT NULL, display_name TEXT, is_default BOOLEAN NOT NULL DEFAULT false, status TEXT NOT NULL DEFAULT 'active', settings JSONB NOT NULL DEFAULT '{}', metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE UNIQUE INDEX IF NOT EXISTS uq_shared_tenants_single_default ON shared.tenants (is_default) WHERE is_default; -- Seed shared tenant for local dev INSERT INTO shared.tenants (tenant_id, name, display_name, status) VALUES ('demo-prod', 'Production', 'Demo Production', 'active') ON CONFLICT (tenant_id) DO NOTHING; -- Release schemas CREATE SCHEMA IF NOT EXISTS release; CREATE SCHEMA IF NOT EXISTS release_app; -- Helper function for updated_at triggers CREATE OR REPLACE FUNCTION release.update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- Tenant isolation function CREATE OR REPLACE FUNCTION release_app.require_current_tenant() RETURNS UUID LANGUAGE plpgsql STABLE SECURITY DEFINER AS $$ DECLARE v_tenant TEXT; BEGIN v_tenant := current_setting('app.tenant_id', true); IF v_tenant IS NULL OR v_tenant = '' THEN RAISE EXCEPTION 'app.tenant_id session variable not set'; END IF; RETURN v_tenant::UUID; END; $$; -- Analytics schema CREATE SCHEMA IF NOT EXISTS analytics; -- ── Regions (bootstrap fallback for release.regions) ── CREATE TABLE IF NOT EXISTS release.regions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES shared.tenants(id) ON DELETE CASCADE, name VARCHAR(100) NOT NULL, display_name VARCHAR(255) NOT NULL, description TEXT, crypto_profile VARCHAR(50) NOT NULL DEFAULT 'international', sort_order INT NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active','decommissioning','archived')), metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by UUID, UNIQUE(tenant_id, name) ); -- ── Infrastructure Bindings (bootstrap fallback) ── CREATE TABLE IF NOT EXISTS release.infrastructure_bindings ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL REFERENCES shared.tenants(id) ON DELETE CASCADE, integration_id UUID, scope_type TEXT NOT NULL CHECK (scope_type IN ('tenant','region','environment')), scope_id UUID, binding_role TEXT NOT NULL CHECK (binding_role IN ('registry','vault','settings_store')), priority INT NOT NULL DEFAULT 0, config_overrides JSONB NOT NULL DEFAULT '{}', is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by UUID ); -- ── Topology Point Status (bootstrap fallback) ── CREATE TABLE IF NOT EXISTS release.topology_point_status ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, target_id UUID, gate_name TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('pending','pass','fail','skip')), message TEXT, details JSONB NOT NULL DEFAULT '{}', checked_at TIMESTAMPTZ, duration_ms INT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- ── Pending Deletions (bootstrap fallback) ── CREATE TABLE IF NOT EXISTS release.pending_deletions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id UUID NOT NULL, entity_type TEXT NOT NULL CHECK (entity_type IN ('tenant','region','environment','target','agent','integration')), entity_id UUID NOT NULL, entity_name TEXT NOT NULL, status TEXT NOT NULL CHECK (status IN ('pending','confirmed','executing','completed','cancelled')), cool_off_hours INT NOT NULL, cool_off_expires_at TIMESTAMPTZ NOT NULL, cascade_summary JSONB NOT NULL DEFAULT '{}', reason TEXT, requested_by UUID NOT NULL, requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), confirmed_by UUID, confirmed_at TIMESTAMPTZ, executed_at TIMESTAMPTZ, completed_at TIMESTAMPTZ, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() );