Files
ModelForge-Public/backend/alembic/versions/20260909_0033_deployed_hub_convergence.py
T

155 lines
6.3 KiB
Python

"""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.