feat: Phase 2 tenant isolation — RLS on 11 session tables #134

Merged
chihlasm merged 16 commits from feat/tenant-isolation-phase-2 into main 2026-04-11 07:02:25 +00:00
Showing only changes of commit 4273ed0e5c - Show all commits

View File

@@ -29,19 +29,35 @@ from app.models.session_branch import SessionBranch # noqa: F401
from app.models.fork_point import ForkPoint # noqa: F401
from app.models.session_handoff import SessionHandoff # noqa: F401
from app.models.session_resolution_output import SessionResolutionOutput # noqa: F401
import os
from app.core.config import settings
def _alembic_sync_url() -> str:
"""Return a psycopg2-compatible sync URL for Alembic.
Prefers ADMIN_DATABASE_URL (resolutionflow_admin, BYPASSRLS) converted to
a sync driver. Falls back to DATABASE_URL_SYNC for local dev where the
admin URL may not be configured separately.
Priority order:
1. Railway native PG env vars (PGHOST/PGPASSWORD/PGUSER/PGDATABASE) — these
are auto-linked per-environment by the Railway PostgreSQL service, so they
always carry the correct superuser credentials for the current environment
(production, PR preview, etc.), including fresh DBs with no custom roles yet.
2. ADMIN_DATABASE_URL (resolutionflow_admin, BYPASSRLS) converted to a sync
driver — used for local dev and stable environments where the role exists.
3. DATABASE_URL_SYNC — last resort / legacy fallback.
"""
pg_host = os.getenv("PGHOST")
pg_user = os.getenv("PGUSER")
pg_password = os.getenv("PGPASSWORD")
pg_db = os.getenv("PGDATABASE")
pg_port = os.getenv("PGPORT", "5432")
if all([pg_host, pg_user, pg_password, pg_db]):
return f"postgresql://{pg_user}:{pg_password}@{pg_host}:{pg_port}/{pg_db}"
admin_url = settings.ADMIN_DATABASE_URL
if admin_url and "+asyncpg" in admin_url:
return admin_url.replace("postgresql+asyncpg://", "postgresql://")
return settings.DATABASE_URL_SYNC