155 lines
6.3 KiB
Python
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.
|