"""Persist one explicitly delegated current/candidate comparison per project binding.""" import sqlalchemy as sa from alembic import op revision = "20260909_0033" down_revision = "20260909_0032" branch_labels = None depends_on = None def upgrade() -> None: op.add_column( "model_update_watches", sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.true()), ) op.add_column( "model_update_watches", sa.Column("version", sa.Integer(), nullable=False, server_default="1"), ) op.create_table( "selected_model_comparisons", sa.Column("id", sa.Uuid(), primary_key=True), sa.Column( "project_binding_id", sa.Uuid(), sa.ForeignKey("project_bindings.id", ondelete="RESTRICT"), nullable=False, ), sa.Column( "consumer_client_id", sa.Uuid(), sa.ForeignKey("service_clients.id", ondelete="RESTRICT"), nullable=False, ), sa.Column( "suite_revision_id", sa.Uuid(), sa.ForeignKey("evaluation_suite_revisions.id", ondelete="RESTRICT"), nullable=False, ), sa.Column( "current_deployment_id", sa.Uuid(), sa.ForeignKey("capability_deployments.id", ondelete="RESTRICT"), nullable=False, ), sa.Column( "candidate_deployment_id", sa.Uuid(), sa.ForeignKey("capability_deployments.id", ondelete="RESTRICT"), nullable=False, ), sa.Column( "candidate_route_id", sa.Uuid(), sa.ForeignKey("capability_experiment_routes.id", ondelete="RESTRICT"), nullable=False, ), sa.Column("idempotency_key", sa.String(128), nullable=False), sa.Column("request_digest", sa.String(64), nullable=False), sa.Column("actor_id", sa.String(128), nullable=False), sa.Column("pins", sa.JSON(), nullable=False), sa.Column("status", sa.String(32), nullable=False), sa.Column("version", sa.Integer(), nullable=False, server_default="1"), sa.Column("current_completed_cases", sa.Integer(), nullable=False, server_default="0"), sa.Column("candidate_completed_cases", sa.Integer(), nullable=False, server_default="0"), sa.Column("expected_cases", sa.Integer(), nullable=False), sa.Column("claim_token_hash", sa.String(64)), sa.Column("claim_expires_at", sa.DateTime(timezone=True)), sa.Column("deadline_at", sa.DateTime(timezone=True)), sa.Column("claimed_at", sa.DateTime(timezone=True)), sa.Column("cancel_requested_at", sa.DateTime(timezone=True)), sa.Column("finished_at", sa.DateTime(timezone=True)), sa.Column("error_code", sa.String(64)), sa.Column("result_digest", sa.String(64)), sa.Column( "baseline_run_id", sa.Uuid(), sa.ForeignKey("evaluation_runs.id", ondelete="RESTRICT") ), sa.Column( "candidate_run_id", sa.Uuid(), sa.ForeignKey("evaluation_runs.id", ondelete="RESTRICT") ), sa.Column( "evaluation_comparison_id", sa.Uuid(), sa.ForeignKey("evaluation_comparisons.id", ondelete="RESTRICT"), ), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False ), sa.UniqueConstraint( "project_binding_id", "idempotency_key", name="uq_selected_comparison_key" ), sa.CheckConstraint( "status IN ('pending', 'running', 'cancel_requested', 'completed', 'failed', 'cancelled', 'interrupted')", name="ck_selected_comparison_status", ), sa.CheckConstraint("expected_cases BETWEEN 1 AND 100", name="ck_selected_comparison_cases"), sa.CheckConstraint( "current_completed_cases BETWEEN 0 AND expected_cases AND candidate_completed_cases BETWEEN 0 AND expected_cases", name="ck_selected_comparison_progress", ), sa.CheckConstraint("version > 0", name="ck_selected_comparison_version"), sa.CheckConstraint( "current_deployment_id <> candidate_deployment_id", name="ck_selected_comparison_distinct_targets", ), ) op.create_index( "uq_selected_comparison_active_binding", "selected_model_comparisons", ["project_binding_id"], unique=True, postgresql_where=sa.text("status IN ('pending', 'running', 'cancel_requested')"), sqlite_where=sa.text("status IN ('pending', 'running', 'cancel_requested')"), ) if op.get_bind().dialect.name == "postgresql": op.execute( "REVOKE DELETE, TRUNCATE ON model_update_watches, model_update_notices FROM modelforge_runtime" ) op.execute( "GRANT SELECT, INSERT, UPDATE ON model_update_watches, model_update_notices TO modelforge_runtime" ) op.execute("REVOKE DELETE, TRUNCATE ON selected_model_comparisons FROM modelforge_runtime") op.execute( "GRANT SELECT, INSERT, UPDATE ON selected_model_comparisons TO modelforge_runtime" ) def downgrade() -> None: # Earlier deployed migrations have destructive downgrades. Never permit this # release to cross back into that graph while authoritative history exists. for table in ( "selected_model_comparisons", "model_update_watches", "model_update_notices", "external_creative_allocations", "external_creative_resources", "external_creative_action_attestations", "external_creative_profile_observations", ): if op.get_bind().scalar(sa.select(sa.func.count()).select_from(sa.table(table))): raise RuntimeError( "Control-plane history exists; restore the pre-migration backup instead." ) op.drop_index("uq_selected_comparison_active_binding", table_name="selected_model_comparisons") op.drop_table("selected_model_comparisons") op.drop_column("model_update_watches", "version") op.drop_column("model_update_watches", "enabled") # Deliberately retain the safer runtime privilege restrictions on empty old tables.