Concelier: - Register Topology.Read, Topology.Manage, Topology.Admin authorization policies mapped to OrchRead/OrchOperate/PlatformContextRead/IntegrationWrite scopes. Previously these policies were referenced by endpoints but never registered, causing System.InvalidOperationException on every topology API call. Gateway routes: - Simplified targets/environments routes (removed specific sub-path routes, use catch-all patterns instead) - Changed environments base route to JobEngine (where CRUD lives) - Changed to ReverseProxy type for all topology routes KNOWN ISSUE (not yet fixed): - ReverseProxy routes don't forward the gateway's identity envelope to Concelier. The regions/targets/bindings endpoints return 401 because hasPrincipal=False — the gateway authenticates the user but doesn't pass the identity to the backend via ReverseProxy. Microservice routes use Valkey transport which includes envelope headers. Topology endpoints need either: (a) Valkey transport registration in Concelier, or (b) Concelier configured to accept raw bearer tokens on ReverseProxy paths. This is an architecture-level fix. Journey findings collected so far: - Integration wizard (Harbor + GitHub App): works end-to-end - Advisory Check All: fixed (parallel individual checks) - Mirror domain creation: works, generate-immediately fails silently - Topology wizard Step 1 (Region): blocked by auth passthrough issue - Topology wizard Step 2 (Environment): POST to JobEngine needs verify - User ID resolution: raw hashes shown everywhere Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
130 lines
4.7 KiB
PL/PgSQL
130 lines
4.7 KiB
PL/PgSQL
-- 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()
|
|
);
|