"""Enable RLS on Phase 4 tables — all remaining tenant-scoped tables. All tables in this migration already have account_id NOT NULL (enforced by earlier migrations). This migration adds ENABLE ROW LEVEL SECURITY, FORCE ROW LEVEL SECURITY, and the appropriate tenant isolation policy to each. Policy variants used: - Standard: account_id = current_setting(app.current_account_id)::uuid - Platform: standard OR account_id = PLATFORM_ACCOUNT_ID (for global content tables readable by all tenants) Skipped intentionally: - accounts — IS the root table; no account_id column - plan_feature_defaults — platform config; no account_id column - script_categories — global lookup table; no account_id column - platform_steps — global content; no account_id column (readable by all) - template_trees — global content; no account_id column (readable by all) Revision ID: b3c7e9f2a1d8 Revises: 172ad76d7d20 Create Date: 2026-04-12 """ from typing import Union from alembic import op revision: str = "b3c7e9f2a1d8" down_revision: Union[str, None] = "172ad76d7d20" branch_labels = None depends_on = None # Standard policy — tenant sees only own rows. _STANDARD_TABLES = [ "users", "account_invites", "account_limit_overrides", "account_feature_overrides", "subscriptions", "ai_chat_sessions", "ai_conversations", "ai_session_steps", "ai_session_embeddings", "ai_suggestions", "ai_usage", "assistant_chats", "attachments", "copilot_conversations", "feedback", "file_uploads", "fork_points", "kb_imports", "notifications", "notification_configs", "notification_logs", "psa_activity_logs", "psa_member_mappings", "script_builder_sessions", "session_ratings", "tree_embeddings", "user_folders", "user_pinned_trees", ] _POLICY_EXPR = ( "account_id = COALESCE(" "NULLIF(current_setting('app.current_account_id', TRUE), ''), " "'00000000-0000-0000-0000-000000000000'" ")::uuid" ) def upgrade() -> None: for table in _STANDARD_TABLES: op.execute(f"ALTER TABLE {table} ENABLE ROW LEVEL SECURITY") op.execute(f"ALTER TABLE {table} FORCE ROW LEVEL SECURITY") op.execute(f""" CREATE POLICY tenant_isolation ON {table} USING ({_POLICY_EXPR}) """) def downgrade() -> None: for table in _STANDARD_TABLES: op.execute(f"DROP POLICY IF EXISTS tenant_isolation ON {table}") op.execute(f"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY")