"""Fix RLS policies — app.tenant_id → app.current_tenant_id. 8 RLS policies in production reference 'app.tenant_id' which doesn't exist as a PostgreSQL parameter. The code uses 'app.current_tenant_id'. This causes 500 errors on roles, sequences, wiki, approval_requests, ai_decision_records, and automation_agent_run_steps. Revision ID: 0136 Revises: 0135 Create Date: 2026-08-21 """ from alembic import op import sqlalchemy as sa revision = "0136" down_revision = "0135" branch_labels = None depends_on = None # All 8 tables with broken RLS policies referencing app.tenant_id TABLES_WITH_BAD_RLS = [ "ai_decision_records", "approval_requests", "automation_agent_run_steps", "roles", "sequences", "wiki_articles", "wiki_article_versions", "wiki_categories", ] def _table_exists(conn, table_name: str) -> bool: """True when the table exists (dual-path convergence, Gate B). Plugin-owned tables may not exist yet on a fresh install when Alembic reaches this revision — skip them instead of failing. The plugin-side convergence migrations apply the same RLS policies. """ row = conn.execute( sa.text("SELECT to_regclass(:tname) IS NOT NULL"), {"tname": f"public.{table_name}"}, ).scalar() return bool(row) def upgrade() -> None: conn = op.get_bind() for table in TABLES_WITH_BAD_RLS: if not _table_exists(conn, table): continue # Drop old policy with app.tenant_id op.execute(f"DROP POLICY IF EXISTS tenant_isolation ON {table};") # Create new policy with app.current_tenant_id op.execute( f"CREATE POLICY tenant_isolation ON {table} " f"USING (tenant_id::text = current_setting('app.current_tenant_id', true));" ) def downgrade() -> None: conn = op.get_bind() for table in TABLES_WITH_BAD_RLS: if not _table_exists(conn, table): continue op.execute(f"DROP POLICY IF EXISTS tenant_isolation ON {table};") op.execute( f"CREATE POLICY tenant_isolation ON {table} " f"USING (tenant_id::text = current_setting('app.tenant_id', true));" )