"""Project onboarding and bounded credential overlap. Revision ID: 20260908_0026 Revises: 20260907_0025 """ import sqlalchemy as sa from alembic import op revision = "20260908_0026" down_revision = "20260907_0025" branch_labels = None depends_on = None def upgrade() -> None: op.create_table( "project_onboarding", sa.Column("project_id", sa.Uuid(), sa.ForeignKey("projects.id", ondelete="RESTRICT"), primary_key=True), sa.Column("owner", sa.String(128), nullable=False), sa.Column("environment", sa.String(32), nullable=False), sa.Column("labels", sa.JSON(), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), ) op.add_column("service_credentials", sa.Column("superseded_at", sa.DateTime(timezone=True))) op.drop_index("uq_active_service_client_credential", table_name="service_credentials") op.create_index( "uq_active_service_client_credential", "service_credentials", ["service_client_id"], unique=True, postgresql_where=sa.text("revoked_at IS NULL AND superseded_at IS NULL"), sqlite_where=sa.text("revoked_at IS NULL AND superseded_at IS NULL"), ) if op.get_bind().dialect.name == "postgresql": op.execute("GRANT SELECT, INSERT ON project_onboarding TO modelforge_runtime") op.execute("REVOKE UPDATE, DELETE, TRUNCATE ON project_onboarding FROM modelforge_runtime") def downgrade() -> None: connection = op.get_bind() if connection.execute(sa.text("SELECT count(*) FROM project_onboarding")).scalar_one() or connection.execute( sa.text("SELECT count(*) FROM service_credentials WHERE superseded_at IS NOT NULL") ).scalar_one(): raise RuntimeError("Onboarding or rotation history exists; recover the pre-migration backup instead.") op.drop_index("uq_active_service_client_credential", table_name="service_credentials") op.create_index( "uq_active_service_client_credential", "service_credentials", ["service_client_id"], unique=True, postgresql_where=sa.text("revoked_at IS NULL"), sqlite_where=sa.text("revoked_at IS NULL"), ) op.drop_column("service_credentials", "superseded_at") op.drop_table("project_onboarding")