"""GEMS Pass 1: domain_id FK on extension_points/technical_debt, repo_id on contributions Revision ID: e1f2a3b4c5d6 Revises: d3e4f5a6b7c8 Create Date: 2026-03-02 00:00:00.000000 """ from typing import Sequence, Union import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql revision: str = "e1f2a3b4c5d6" down_revision: Union[str, None] = "d3e4f5a6b7c8" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: # ── extension_points: add domain_id FK ──────────────────────────────────── op.add_column( "extension_points", sa.Column("domain_id", postgresql.UUID(as_uuid=True), nullable=True), ) op.create_foreign_key( "fk_ep_domain_id", "extension_points", "domains", ["domain_id"], ["id"], ondelete="RESTRICT", ) # Backfill from slug string op.execute(""" UPDATE extension_points ep SET domain_id = d.id FROM domains d WHERE d.slug = ep.domain """) # Safety check: abort if any rows remain unmatched op.execute(""" DO $$ BEGIN IF EXISTS (SELECT 1 FROM extension_points WHERE domain_id IS NULL) THEN RAISE EXCEPTION 'GEMS Pass 1: extension_points rows with unknown domain slug: %', (SELECT string_agg(DISTINCT domain, ', ') FROM extension_points WHERE domain_id IS NULL); END IF; END $$; """) op.alter_column("extension_points", "domain_id", nullable=False) op.drop_index("ix_extension_points_domain", table_name="extension_points") op.drop_column("extension_points", "domain") op.create_index("ix_extension_points_domain_id", "extension_points", ["domain_id"]) # ── technical_debt: add domain_id FK ────────────────────────────────────── op.add_column( "technical_debt", sa.Column("domain_id", postgresql.UUID(as_uuid=True), nullable=True), ) op.create_foreign_key( "fk_td_domain_id", "technical_debt", "domains", ["domain_id"], ["id"], ondelete="RESTRICT", ) op.execute(""" UPDATE technical_debt td SET domain_id = d.id FROM domains d WHERE d.slug = td.domain """) op.execute(""" DO $$ BEGIN IF EXISTS (SELECT 1 FROM technical_debt WHERE domain_id IS NULL) THEN RAISE EXCEPTION 'GEMS Pass 1: technical_debt rows with unknown domain slug: %', (SELECT string_agg(DISTINCT domain, ', ') FROM technical_debt WHERE domain_id IS NULL); END IF; END $$; """) op.alter_column("technical_debt", "domain_id", nullable=False) op.drop_index("ix_technical_debt_domain", table_name="technical_debt") op.drop_column("technical_debt", "domain") op.create_index("ix_technical_debt_domain_id", "technical_debt", ["domain_id"]) # ── contributions: add nullable repo_id FK ──────────────────────────────── op.add_column( "contributions", sa.Column( "repo_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("managed_repos.id", ondelete="SET NULL"), nullable=True, ), ) def downgrade() -> None: # contributions: drop repo_id op.drop_column("contributions", "repo_id") # technical_debt: restore domain string op.add_column( "technical_debt", sa.Column("domain", sa.String(50), nullable=True), ) op.execute(""" UPDATE technical_debt td SET domain = d.slug FROM domains d WHERE d.id = td.domain_id """) op.alter_column("technical_debt", "domain", nullable=False) op.create_index("ix_technical_debt_domain", "technical_debt", ["domain"]) op.drop_index("ix_technical_debt_domain_id", table_name="technical_debt") op.drop_constraint("fk_td_domain_id", "technical_debt", type_="foreignkey") op.drop_column("technical_debt", "domain_id") # extension_points: restore domain string op.add_column( "extension_points", sa.Column("domain", sa.String(50), nullable=True), ) op.execute(""" UPDATE extension_points ep SET domain = d.slug FROM domains d WHERE d.id = ep.domain_id """) op.alter_column("extension_points", "domain", nullable=False) op.create_index("ix_extension_points_domain", "extension_points", ["domain"]) op.drop_index("ix_extension_points_domain_id", table_name="extension_points") op.drop_constraint("fk_ep_domain_id", "extension_points", type_="foreignkey") op.drop_column("extension_points", "domain_id")