"""Add audited compute-node decommission tombstones and operation records. Revision ID: 20260828_0022 Revises: 20260827_0021 """ from collections.abc import Sequence import sqlalchemy as sa from alembic import op revision: str = "20260828_0022" down_revision: str | None = "20260827_0021" branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: op.add_column( "compute_nodes", sa.Column("generation", sa.BigInteger(), nullable=False, server_default="1"), ) op.add_column("compute_nodes", sa.Column("decommissioned_at", sa.DateTime(timezone=True))) op.add_column("compute_nodes", sa.Column("decommission_reason", sa.Text())) op.add_column("compute_nodes", sa.Column("decommissioned_by", sa.String(255))) op.create_index("ix_compute_nodes_decommissioned_at", "compute_nodes", ["decommissioned_at"]) op.create_table( "node_decommission_operations", sa.Column("id", sa.Uuid(), primary_key=True, nullable=False), sa.Column( "compute_node_id", sa.Uuid(), sa.ForeignKey("compute_nodes.id", ondelete="RESTRICT"), nullable=False, ), sa.Column("persisted_identity", sa.String(128), nullable=False), sa.Column("idempotency_key", sa.String(128), nullable=False), sa.Column("expected_generation", sa.BigInteger(), nullable=False), sa.Column("preview_digest", sa.String(64), nullable=False), sa.Column("reason", sa.Text(), nullable=False), sa.Column("operator", sa.String(255), nullable=False), sa.Column("previous_state", sa.JSON(), nullable=False), sa.Column("cleanup_summary", sa.JSON(), nullable=False), sa.Column("status", sa.String(32), nullable=False, server_default="completed"), sa.Column( "created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), ), sa.Column("decommissioned_at", sa.DateTime(timezone=True), nullable=False), sa.UniqueConstraint("compute_node_id", name="uq_node_decommission_node"), sa.UniqueConstraint("idempotency_key", name="uq_node_decommission_idempotency"), sa.CheckConstraint("expected_generation >= 1", name="ck_node_decommission_generation"), ) for column in ("compute_node_id", "persisted_identity", "status", "decommissioned_at"): op.create_index( f"ix_node_decommission_operations_{column}", "node_decommission_operations", [column], ) def downgrade() -> None: op.drop_table("node_decommission_operations") op.drop_index("ix_compute_nodes_decommissioned_at", table_name="compute_nodes") op.drop_column("compute_nodes", "decommissioned_by") op.drop_column("compute_nodes", "decommission_reason") op.drop_column("compute_nodes", "decommissioned_at") op.drop_column("compute_nodes", "generation")