"""Enforce workspace isolation in the database rather than in every query. Until now isolation was a convention: each query had to remember to filter by `tenant_id`. Finding S-5 was one that forgot, and forgetting was possible because nothing outside the developer's memory was checking. **This migration is inert until the application connects as a non-owner role.** A PostgreSQL superuser bypasses row-level security unconditionally, and the table owner does too — which is why every table here is FORCEd, and why `scripts/create_app_role.py` exists. Applying this while still connecting as the owner turns the policies on for that connection as well, so the code that sets `app.tenant_id` has to be in place first. It ships in the same change. Two settings drive the policies: app.tenant_id the workspace, or '' when none is set app.bypass_rls 'on' for deliberate cross-workspace work Unset means '' means no rows. A code path that forgets to establish context therefore fails loudly rather than quietly reading everyone's data. Revision ID: c3d5e7f9a801 Revises: b2e1d4f5a602 """ from typing import Sequence, Union from alembic import op revision: str = "c3d5e7f9a801" down_revision: Union[str, Sequence[str], None] = "b2e1d4f5a602" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None BYPASS = "current_setting('app.bypass_rls', true) = 'on'" CURRENT = "NULLIF(current_setting('app.tenant_id', true), '')::uuid" STRICT = ("users", "tenant_modules") SHARED_NULLS = ("roles", "sso_grants") def upgrade() -> None: for table in STRICT: 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 ({BYPASS} OR tenant_id = {CURRENT}) WITH CHECK ({BYPASS} OR tenant_id = {CURRENT}) """ ) for table in SHARED_NULLS: 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 ({BYPASS} OR tenant_id IS NULL OR tenant_id = {CURRENT}) WITH CHECK ({BYPASS} OR tenant_id = {CURRENT}) """ ) def downgrade() -> None: for table in (*STRICT, *SHARED_NULLS): op.execute(f"DROP POLICY IF EXISTS tenant_isolation ON {table}") op.execute(f"ALTER TABLE {table} NO FORCE ROW LEVEL SECURITY") op.execute(f"ALTER TABLE {table} DISABLE ROW LEVEL SECURITY")