-- Authority Schema: Consolidated Initial Schema -- Consolidated from migrations 001-005 (pre_1.0 archived) -- Creates the complete authority schema for IAM, tenants, users, tokens, RLS, and audit -- ============================================================================ -- SECTION 1: Schema Creation -- ============================================================================ CREATE SCHEMA IF NOT EXISTS authority; CREATE SCHEMA IF NOT EXISTS authority_app; -- ============================================================================ -- SECTION 2: Helper Functions -- ============================================================================ -- Function to update updated_at timestamp CREATE OR REPLACE FUNCTION authority.update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$ LANGUAGE plpgsql; -- Tenant context helper function for RLS CREATE OR REPLACE FUNCTION authority_app.require_current_tenant() RETURNS TEXT 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' USING HINT = 'Set via: SELECT set_config(''app.tenant_id'', '''', false)', ERRCODE = 'P0001'; END IF; RETURN v_tenant; END; $$; REVOKE ALL ON FUNCTION authority_app.require_current_tenant() FROM PUBLIC; -- ============================================================================ -- SECTION 3: Core Tables -- ============================================================================ -- Tenants table (NOT RLS-protected - defines tenant boundaries) CREATE TABLE IF NOT EXISTS authority.tenants ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL UNIQUE, name TEXT NOT NULL, display_name TEXT, status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'suspended', 'deleted')), settings JSONB NOT NULL DEFAULT '{}', metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT, updated_by TEXT ); CREATE INDEX idx_tenants_status ON authority.tenants(status); CREATE INDEX idx_tenants_created_at ON authority.tenants(created_at); COMMENT ON TABLE authority.tenants IS 'Tenant registry. Not RLS-protected - defines tenant boundaries for the system.'; -- Users table CREATE TABLE IF NOT EXISTS authority.users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id), username TEXT NOT NULL, email TEXT, display_name TEXT, password_hash TEXT, password_salt TEXT, enabled BOOLEAN NOT NULL DEFAULT TRUE, password_algorithm TEXT DEFAULT 'argon2id', status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'inactive', 'locked', 'deleted')), email_verified BOOLEAN NOT NULL DEFAULT FALSE, mfa_enabled BOOLEAN NOT NULL DEFAULT FALSE, mfa_secret TEXT, mfa_backup_codes TEXT, failed_login_attempts INT NOT NULL DEFAULT 0, locked_until TIMESTAMPTZ, last_login_at TIMESTAMPTZ, password_changed_at TIMESTAMPTZ, last_password_change_at TIMESTAMPTZ, password_expires_at TIMESTAMPTZ, settings JSONB NOT NULL DEFAULT '{}', metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_by TEXT, updated_by TEXT, UNIQUE(tenant_id, username), UNIQUE(tenant_id, email) ); CREATE INDEX idx_users_tenant_id ON authority.users(tenant_id); CREATE INDEX idx_users_status ON authority.users(tenant_id, status); CREATE INDEX idx_users_email ON authority.users(tenant_id, email); -- Roles table CREATE TABLE IF NOT EXISTS authority.roles ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id), name TEXT NOT NULL, display_name TEXT, description TEXT, is_system BOOLEAN NOT NULL DEFAULT FALSE, metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(tenant_id, name) ); CREATE INDEX idx_roles_tenant_id ON authority.roles(tenant_id); -- Permissions table CREATE TABLE IF NOT EXISTS authority.permissions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id), name TEXT NOT NULL, resource TEXT NOT NULL, action TEXT NOT NULL, description TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE(tenant_id, name) ); CREATE INDEX idx_permissions_tenant_id ON authority.permissions(tenant_id); CREATE INDEX idx_permissions_resource ON authority.permissions(tenant_id, resource); -- Role-Permission assignments CREATE TABLE IF NOT EXISTS authority.role_permissions ( role_id UUID NOT NULL REFERENCES authority.roles(id) ON DELETE CASCADE, permission_id UUID NOT NULL REFERENCES authority.permissions(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (role_id, permission_id) ); -- User-Role assignments CREATE TABLE IF NOT EXISTS authority.user_roles ( user_id UUID NOT NULL REFERENCES authority.users(id) ON DELETE CASCADE, role_id UUID NOT NULL REFERENCES authority.roles(id) ON DELETE CASCADE, granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), granted_by TEXT, expires_at TIMESTAMPTZ, PRIMARY KEY (user_id, role_id) ); -- API Keys table CREATE TABLE IF NOT EXISTS authority.api_keys ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id), user_id UUID REFERENCES authority.users(id) ON DELETE CASCADE, name TEXT NOT NULL, key_hash TEXT NOT NULL, key_prefix TEXT NOT NULL, scopes TEXT[] NOT NULL DEFAULT '{}', status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked', 'expired')), last_used_at TIMESTAMPTZ, expires_at TIMESTAMPTZ, metadata JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), revoked_at TIMESTAMPTZ, revoked_by TEXT ); CREATE INDEX idx_api_keys_tenant_id ON authority.api_keys(tenant_id); CREATE INDEX idx_api_keys_key_prefix ON authority.api_keys(key_prefix); CREATE INDEX idx_api_keys_user_id ON authority.api_keys(user_id); CREATE INDEX idx_api_keys_status ON authority.api_keys(tenant_id, status); -- Tokens table (access tokens) CREATE TABLE IF NOT EXISTS authority.tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id), user_id UUID REFERENCES authority.users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, token_type TEXT NOT NULL DEFAULT 'access' CHECK (token_type IN ('access', 'refresh', 'api')), scopes TEXT[] NOT NULL DEFAULT '{}', client_id TEXT, issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL, revoked_at TIMESTAMPTZ, revoked_by TEXT, metadata JSONB NOT NULL DEFAULT '{}' ); CREATE INDEX idx_tokens_tenant_id ON authority.tokens(tenant_id); CREATE INDEX idx_tokens_user_id ON authority.tokens(user_id); CREATE INDEX idx_tokens_expires_at ON authority.tokens(expires_at); CREATE INDEX idx_tokens_token_hash ON authority.tokens(token_hash); -- Refresh Tokens table CREATE TABLE IF NOT EXISTS authority.refresh_tokens ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id), user_id UUID NOT NULL REFERENCES authority.users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, access_token_id UUID REFERENCES authority.tokens(id) ON DELETE SET NULL, client_id TEXT, issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL, revoked_at TIMESTAMPTZ, revoked_by TEXT, replaced_by UUID, metadata JSONB NOT NULL DEFAULT '{}' ); CREATE INDEX idx_refresh_tokens_tenant_id ON authority.refresh_tokens(tenant_id); CREATE INDEX idx_refresh_tokens_user_id ON authority.refresh_tokens(user_id); CREATE INDEX idx_refresh_tokens_expires_at ON authority.refresh_tokens(expires_at); -- Sessions table CREATE TABLE IF NOT EXISTS authority.sessions ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), tenant_id TEXT NOT NULL REFERENCES authority.tenants(tenant_id), user_id UUID NOT NULL REFERENCES authority.users(id) ON DELETE CASCADE, session_token_hash TEXT NOT NULL UNIQUE, ip_address TEXT, user_agent TEXT, started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), last_activity_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), expires_at TIMESTAMPTZ NOT NULL, ended_at TIMESTAMPTZ, end_reason TEXT, metadata JSONB NOT NULL DEFAULT '{}' ); CREATE INDEX idx_sessions_tenant_id ON authority.sessions(tenant_id); CREATE INDEX idx_sessions_user_id ON authority.sessions(user_id); CREATE INDEX idx_sessions_expires_at ON authority.sessions(expires_at); -- Audit log table CREATE TABLE IF NOT EXISTS authority.audit ( id BIGSERIAL PRIMARY KEY, tenant_id TEXT NOT NULL, user_id UUID, action TEXT NOT NULL, resource_type TEXT NOT NULL, resource_id TEXT, old_value JSONB, new_value JSONB, ip_address TEXT, user_agent TEXT, correlation_id TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_audit_tenant_id ON authority.audit(tenant_id); CREATE INDEX idx_audit_user_id ON authority.audit(user_id); CREATE INDEX idx_audit_action ON authority.audit(action); CREATE INDEX idx_audit_resource ON authority.audit(resource_type, resource_id); CREATE INDEX idx_audit_created_at ON authority.audit(created_at); CREATE INDEX idx_audit_correlation_id ON authority.audit(correlation_id); -- ============================================================================ -- SECTION 4: OIDC and Mongo Store Equivalent Tables -- ============================================================================ -- Bootstrap invites CREATE TABLE IF NOT EXISTS authority.bootstrap_invites ( id TEXT PRIMARY KEY, token TEXT NOT NULL UNIQUE, type TEXT NOT NULL, provider TEXT, target TEXT, expires_at TIMESTAMPTZ NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), issued_by TEXT, reserved_until TIMESTAMPTZ, reserved_by TEXT, consumed BOOLEAN NOT NULL DEFAULT FALSE, status TEXT NOT NULL DEFAULT 'pending', metadata JSONB NOT NULL DEFAULT '{}' ); -- Service accounts CREATE TABLE IF NOT EXISTS authority.service_accounts ( id TEXT PRIMARY KEY, account_id TEXT NOT NULL UNIQUE, tenant TEXT NOT NULL, display_name TEXT NOT NULL, description TEXT, enabled BOOLEAN NOT NULL DEFAULT TRUE, allowed_scopes TEXT[] NOT NULL DEFAULT '{}', authorized_clients TEXT[] NOT NULL DEFAULT '{}', attributes JSONB NOT NULL DEFAULT '{}', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_service_accounts_tenant ON authority.service_accounts(tenant); -- Clients CREATE TABLE IF NOT EXISTS authority.clients ( id TEXT PRIMARY KEY, client_id TEXT NOT NULL UNIQUE, client_secret TEXT, secret_hash TEXT, display_name TEXT, description TEXT, plugin TEXT, sender_constraint TEXT, enabled BOOLEAN NOT NULL DEFAULT TRUE, redirect_uris TEXT[] NOT NULL DEFAULT '{}', post_logout_redirect_uris TEXT[] NOT NULL DEFAULT '{}', allowed_scopes TEXT[] NOT NULL DEFAULT '{}', allowed_grant_types TEXT[] NOT NULL DEFAULT '{}', require_client_secret BOOLEAN NOT NULL DEFAULT TRUE, require_pkce BOOLEAN NOT NULL DEFAULT FALSE, allow_plain_text_pkce BOOLEAN NOT NULL DEFAULT FALSE, client_type TEXT, properties JSONB NOT NULL DEFAULT '{}', certificate_bindings JSONB NOT NULL DEFAULT '[]', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Revocations CREATE TABLE IF NOT EXISTS authority.revocations ( id TEXT PRIMARY KEY, category TEXT NOT NULL, revocation_id TEXT NOT NULL, subject_id TEXT, client_id TEXT, token_id TEXT, reason TEXT NOT NULL, reason_description TEXT, revoked_at TIMESTAMPTZ NOT NULL, effective_at TIMESTAMPTZ NOT NULL, expires_at TIMESTAMPTZ, metadata JSONB NOT NULL DEFAULT '{}' ); CREATE UNIQUE INDEX IF NOT EXISTS idx_revocations_category_revocation_id ON authority.revocations(category, revocation_id); -- Login attempts CREATE TABLE IF NOT EXISTS authority.login_attempts ( id TEXT PRIMARY KEY, subject_id TEXT, client_id TEXT, event_type TEXT NOT NULL, outcome TEXT NOT NULL, reason TEXT, ip_address TEXT, user_agent TEXT, occurred_at TIMESTAMPTZ NOT NULL, properties JSONB NOT NULL DEFAULT '[]' ); CREATE INDEX IF NOT EXISTS idx_login_attempts_subject ON authority.login_attempts(subject_id, occurred_at DESC); -- OIDC tokens CREATE TABLE IF NOT EXISTS authority.oidc_tokens ( id TEXT PRIMARY KEY, token_id TEXT NOT NULL UNIQUE, subject_id TEXT, client_id TEXT, token_type TEXT NOT NULL, reference_id TEXT, created_at TIMESTAMPTZ NOT NULL, expires_at TIMESTAMPTZ, redeemed_at TIMESTAMPTZ, payload TEXT, properties JSONB NOT NULL DEFAULT '{}' ); CREATE INDEX IF NOT EXISTS idx_oidc_tokens_subject ON authority.oidc_tokens(subject_id); CREATE INDEX IF NOT EXISTS idx_oidc_tokens_client ON authority.oidc_tokens(client_id); CREATE INDEX IF NOT EXISTS idx_oidc_tokens_reference ON authority.oidc_tokens(reference_id); -- OIDC refresh tokens CREATE TABLE IF NOT EXISTS authority.oidc_refresh_tokens ( id TEXT PRIMARY KEY, token_id TEXT NOT NULL UNIQUE, subject_id TEXT, client_id TEXT, handle TEXT, created_at TIMESTAMPTZ NOT NULL, expires_at TIMESTAMPTZ, consumed_at TIMESTAMPTZ, payload TEXT ); CREATE INDEX IF NOT EXISTS idx_oidc_refresh_tokens_subject ON authority.oidc_refresh_tokens(subject_id); CREATE INDEX IF NOT EXISTS idx_oidc_refresh_tokens_handle ON authority.oidc_refresh_tokens(handle); -- Airgap audit CREATE TABLE IF NOT EXISTS authority.airgap_audit ( id TEXT PRIMARY KEY, event_type TEXT NOT NULL, operator_id TEXT, component_id TEXT, outcome TEXT NOT NULL, reason TEXT, occurred_at TIMESTAMPTZ NOT NULL, properties JSONB NOT NULL DEFAULT '[]' ); CREATE INDEX IF NOT EXISTS idx_airgap_audit_occurred_at ON authority.airgap_audit(occurred_at DESC); -- Revocation export state (singleton row with optimistic concurrency) CREATE TABLE IF NOT EXISTS authority.revocation_export_state ( id INT PRIMARY KEY DEFAULT 1, sequence BIGINT NOT NULL DEFAULT 0, bundle_id TEXT, issued_at TIMESTAMPTZ ); -- Offline Kit Audit CREATE TABLE IF NOT EXISTS authority.offline_kit_audit ( event_id UUID PRIMARY KEY, tenant_id TEXT NOT NULL, event_type TEXT NOT NULL, timestamp TIMESTAMPTZ NOT NULL, actor TEXT NOT NULL, details JSONB NOT NULL, result TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_offline_kit_audit_ts ON authority.offline_kit_audit(timestamp DESC); CREATE INDEX IF NOT EXISTS idx_offline_kit_audit_type ON authority.offline_kit_audit(event_type); CREATE INDEX IF NOT EXISTS idx_offline_kit_audit_tenant_ts ON authority.offline_kit_audit(tenant_id, timestamp DESC); CREATE INDEX IF NOT EXISTS idx_offline_kit_audit_result ON authority.offline_kit_audit(tenant_id, result, timestamp DESC); -- Verdict manifests table CREATE TABLE IF NOT EXISTS authority.verdict_manifests ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), manifest_id TEXT NOT NULL, tenant TEXT NOT NULL, asset_digest TEXT NOT NULL, vulnerability_id TEXT NOT NULL, inputs_json JSONB NOT NULL, status TEXT NOT NULL CHECK (status IN ('affected', 'not_affected', 'fixed', 'under_investigation')), confidence DOUBLE PRECISION NOT NULL CHECK (confidence >= 0 AND confidence <= 1), result_json JSONB NOT NULL, policy_hash TEXT NOT NULL, lattice_version TEXT NOT NULL, evaluated_at TIMESTAMPTZ NOT NULL, manifest_digest TEXT NOT NULL, signature_base64 TEXT, rekor_log_id TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), CONSTRAINT uq_verdict_manifest_id UNIQUE (tenant, manifest_id) ); CREATE INDEX IF NOT EXISTS idx_verdict_asset_vuln ON authority.verdict_manifests(tenant, asset_digest, vulnerability_id); CREATE INDEX IF NOT EXISTS idx_verdict_policy ON authority.verdict_manifests(tenant, policy_hash, lattice_version); CREATE INDEX IF NOT EXISTS idx_verdict_time ON authority.verdict_manifests USING BRIN (evaluated_at); CREATE UNIQUE INDEX IF NOT EXISTS idx_verdict_replay ON authority.verdict_manifests(tenant, asset_digest, vulnerability_id, policy_hash, lattice_version); CREATE INDEX IF NOT EXISTS idx_verdict_digest ON authority.verdict_manifests(manifest_digest); COMMENT ON TABLE authority.verdict_manifests IS 'VEX verdict manifests for deterministic replay verification'; -- ============================================================================ -- SECTION 4b: Seed Default Tenant -- ============================================================================ INSERT INTO authority.tenants (tenant_id, name, display_name, status) VALUES ('demo-prod', 'Production', 'Demo Production', 'active') ON CONFLICT (tenant_id) DO NOTHING; -- ============================================================================ -- SECTION 5: Triggers -- ============================================================================ CREATE TRIGGER trg_tenants_updated_at BEFORE UPDATE ON authority.tenants FOR EACH ROW EXECUTE FUNCTION authority.update_updated_at(); CREATE TRIGGER trg_users_updated_at BEFORE UPDATE ON authority.users FOR EACH ROW EXECUTE FUNCTION authority.update_updated_at(); CREATE TRIGGER trg_roles_updated_at BEFORE UPDATE ON authority.roles FOR EACH ROW EXECUTE FUNCTION authority.update_updated_at(); -- ============================================================================ -- SECTION 6: Row-Level Security -- ============================================================================ -- authority.users ALTER TABLE authority.users ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.users FORCE ROW LEVEL SECURITY; CREATE POLICY users_tenant_isolation ON authority.users FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.roles ALTER TABLE authority.roles ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.roles FORCE ROW LEVEL SECURITY; CREATE POLICY roles_tenant_isolation ON authority.roles FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.permissions ALTER TABLE authority.permissions ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.permissions FORCE ROW LEVEL SECURITY; CREATE POLICY permissions_tenant_isolation ON authority.permissions FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.role_permissions (FK-based, inherits from roles) ALTER TABLE authority.role_permissions ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.role_permissions FORCE ROW LEVEL SECURITY; CREATE POLICY role_permissions_tenant_isolation ON authority.role_permissions FOR ALL USING ( role_id IN ( SELECT id FROM authority.roles WHERE tenant_id = authority_app.require_current_tenant() ) ); -- authority.user_roles (FK-based, inherits from users) ALTER TABLE authority.user_roles ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.user_roles FORCE ROW LEVEL SECURITY; CREATE POLICY user_roles_tenant_isolation ON authority.user_roles FOR ALL USING ( user_id IN ( SELECT id FROM authority.users WHERE tenant_id = authority_app.require_current_tenant() ) ); -- authority.api_keys ALTER TABLE authority.api_keys ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.api_keys FORCE ROW LEVEL SECURITY; CREATE POLICY api_keys_tenant_isolation ON authority.api_keys FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.tokens ALTER TABLE authority.tokens ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.tokens FORCE ROW LEVEL SECURITY; CREATE POLICY tokens_tenant_isolation ON authority.tokens FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.refresh_tokens ALTER TABLE authority.refresh_tokens ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.refresh_tokens FORCE ROW LEVEL SECURITY; CREATE POLICY refresh_tokens_tenant_isolation ON authority.refresh_tokens FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.sessions ALTER TABLE authority.sessions ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.sessions FORCE ROW LEVEL SECURITY; CREATE POLICY sessions_tenant_isolation ON authority.sessions FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.audit ALTER TABLE authority.audit ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.audit FORCE ROW LEVEL SECURITY; CREATE POLICY audit_tenant_isolation ON authority.audit FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.offline_kit_audit ALTER TABLE authority.offline_kit_audit ENABLE ROW LEVEL SECURITY; ALTER TABLE authority.offline_kit_audit FORCE ROW LEVEL SECURITY; CREATE POLICY offline_kit_audit_tenant_isolation ON authority.offline_kit_audit FOR ALL USING (tenant_id = authority_app.require_current_tenant()) WITH CHECK (tenant_id = authority_app.require_current_tenant()); -- authority.verdict_manifests ALTER TABLE authority.verdict_manifests ENABLE ROW LEVEL SECURITY; CREATE POLICY verdict_tenant_isolation ON authority.verdict_manifests USING (tenant = current_setting('app.current_tenant', true)) WITH CHECK (tenant = current_setting('app.current_tenant', true)); -- ============================================================================ -- SECTION 7: Roles and Permissions -- ============================================================================ DO $$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authority_admin') THEN CREATE ROLE authority_admin WITH NOLOGIN BYPASSRLS; END IF; END $$; -- Grant permissions (if role exists) DO $$ BEGIN IF EXISTS (SELECT FROM pg_roles WHERE rolname = 'stellaops_app') THEN GRANT SELECT, INSERT, UPDATE, DELETE ON authority.verdict_manifests TO stellaops_app; GRANT USAGE ON SCHEMA authority TO stellaops_app; END IF; END $$; -- ============================================================================ -- SECTION 8: Demo Seed Data -- ============================================================================ -- Roles for demo-prod tenant INSERT INTO authority.roles (id, tenant_id, name, display_name, description, is_system) VALUES ('a0000002-0000-0000-0000-000000000001', 'demo-prod', 'admin', 'Administrator', 'Full platform access', true), ('a0000002-0000-0000-0000-000000000002', 'demo-prod', 'operator', 'Operator', 'Release and deployment operations', true), ('a0000002-0000-0000-0000-000000000003', 'demo-prod', 'viewer', 'Viewer', 'Read-only access', true) ON CONFLICT (tenant_id, name) DO NOTHING; -- OAuth Clients INSERT INTO authority.clients (id, client_id, display_name, description, enabled, redirect_uris, post_logout_redirect_uris, allowed_scopes, allowed_grant_types, require_client_secret, require_pkce, properties) VALUES ('demo-client-ui', 'stella-ops-ui', 'Stella Ops Console', 'Web UI application', true, ARRAY['https://stella-ops.local/auth/callback', 'https://stella-ops.local/auth/silent-refresh', 'https://127.1.0.1/auth/callback', 'https://127.1.0.1/auth/silent-refresh'], ARRAY['https://stella-ops.local/', 'https://127.1.0.1/'], ARRAY['openid', 'profile', 'email', 'offline_access', 'ui.read', 'ui.admin', 'ui.preferences.read', 'ui.preferences.write', 'authority:tenants.read', 'authority:users.read', 'authority:roles.read', 'authority:clients.read', 'authority:tokens.read', 'authority:branding.read', 'authority.audit.read', 'graph:read', 'sbom:read', 'scanner:read', 'policy:read', 'policy:simulate', 'policy:author', 'policy:review', 'policy:approve', 'policy:run', 'policy:activate', 'policy:audit', 'policy:edit', 'policy:operate', 'policy:publish', 'airgap:seal', 'airgap:status:read', 'orch:read', 'analytics.read', 'advisory:read', 'advisory-ai:view', 'advisory-ai:operate', 'vex:read', 'vexhub:read', 'exceptions:read', 'exceptions:approve', 'aoc:verify', 'findings:read', 'release:read', 'release:write', 'release:publish', 'scheduler:read', 'scheduler:operate', 'notify.viewer', 'notify.operator', 'notify.admin', 'notify.escalate', 'evidence:read', 'export.viewer', 'export.operator', 'export.admin', 'vuln:view', 'vuln:investigate', 'vuln:operate', 'vuln:audit', 'platform.context.read', 'platform.context.write', 'doctor:run', 'doctor:admin', 'ops.health', 'integration:read', 'integration:write', 'integration:operate', 'registry.admin', 'timeline:read', 'timeline:write'], ARRAY['authorization_code', 'refresh_token'], false, true, '{"tenant": "demo-prod"}'::jsonb) ON CONFLICT (client_id) DO NOTHING;