ALTER TABLE gateway_identity_configuration_revisions DROP CONSTRAINT IF EXISTS gateway_identity_configuration_revisions_schema_version_check; ALTER TABLE gateway_identity_configuration_revisions ADD COLUMN IF NOT EXISTS tenant_mode text NOT NULL DEFAULT 'single_tenant', ADD COLUMN IF NOT EXISTS tenant_context_endpoint text, ADD COLUMN IF NOT EXISTS tenant_context_audience text, ADD COLUMN IF NOT EXISTS tenant_context_scope text, ADD CONSTRAINT gateway_identity_configuration_revisions_schema_version_check CHECK (schema_version IN (1,2)), ADD CONSTRAINT gateway_identity_configuration_revisions_tenant_mode_check CHECK (tenant_mode IN ('single_tenant','multi_tenant')), ADD CONSTRAINT gateway_identity_configuration_revisions_manifest_mode_check CHECK ( (state='draft' AND issuer IS NULL) OR (schema_version=1 AND tenant_mode='single_tenant' AND tenant_id IS NOT NULL AND local_tenant_key <> '' AND tenant_context_endpoint IS NULL AND tenant_context_audience IS NULL AND tenant_context_scope IS NULL) OR (schema_version=2 AND tenant_mode='multi_tenant' AND tenant_id IS NULL AND local_tenant_key='' AND tenant_context_endpoint IS NOT NULL AND tenant_context_audience='urn:easyai:auth-center:tenant-context' AND tenant_context_scope='tenant.context.read') ) NOT VALID; CREATE TABLE IF NOT EXISTS gateway_oidc_tenant_bindings ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), issuer text NOT NULL, application_id text NOT NULL, external_tenant_id text NOT NULL, gateway_tenant_id uuid NOT NULL REFERENCES gateway_tenants(id) ON DELETE CASCADE, access_status text NOT NULL DEFAULT 'active' CHECK (access_status IN ('active','disabled')), metadata_status text NOT NULL DEFAULT 'metadata_pending' CHECK (metadata_status IN ('metadata_pending','synced','rejected')), display_name text, slug text, metadata_version text, metadata_etag text, metadata_updated_at timestamptz, last_sync_at timestamptz, next_sync_at timestamptz NOT NULL DEFAULT now(), sync_failure_count integer NOT NULL DEFAULT 0 CHECK (sync_failure_count >= 0), last_error_category text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE(issuer,application_id,external_tenant_id), UNIQUE(id,gateway_tenant_id) ); CREATE INDEX IF NOT EXISTS idx_gateway_oidc_tenant_bindings_sync ON gateway_oidc_tenant_bindings(next_sync_at) WHERE access_status='active'; CREATE TABLE IF NOT EXISTS gateway_oidc_user_bindings ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), tenant_binding_id uuid NOT NULL REFERENCES gateway_oidc_tenant_bindings(id) ON DELETE CASCADE, subject text NOT NULL, gateway_user_id uuid NOT NULL REFERENCES gateway_users(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), UNIQUE(tenant_binding_id,subject), UNIQUE(id,gateway_user_id) ); ALTER TABLE gateway_oidc_sessions ADD COLUMN IF NOT EXISTS oidc_user_binding_id uuid REFERENCES gateway_oidc_user_bindings(id) ON DELETE CASCADE, ADD COLUMN IF NOT EXISTS oidc_client_id text; CREATE INDEX IF NOT EXISTS idx_gateway_oidc_sessions_user_binding ON gateway_oidc_sessions(oidc_user_binding_id,created_at DESC) WHERE oidc_user_binding_id IS NOT NULL; ALTER TABLE gateway_security_event_receipts ADD COLUMN IF NOT EXISTS application_id text, ADD COLUMN IF NOT EXISTS subject_type text CHECK (subject_type IS NULL OR subject_type IN ('principal','tenant')); ALTER TABLE gateway_oidc_revocation_watermarks ADD COLUMN IF NOT EXISTS application_id text NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS subject_type text NOT NULL DEFAULT 'principal' CHECK (subject_type IN ('principal','tenant')); ALTER TABLE gateway_oidc_revocation_watermarks DROP CONSTRAINT IF EXISTS gateway_oidc_revocation_watermarks_pkey; ALTER TABLE gateway_oidc_revocation_watermarks ADD PRIMARY KEY (issuer,application_id,tenant_id,subject_type,subject); DROP INDEX IF EXISTS idx_gateway_oidc_revocation_watermarks_lookup; CREATE INDEX idx_gateway_oidc_revocation_watermarks_lookup ON gateway_oidc_revocation_watermarks(issuer,application_id,tenant_id,subject_type,subject,revoked_at);