432 lines
18 KiB
Python
432 lines
18 KiB
Python
"""Add evidence-bound lifecycle, rollback, retention and cleanup records.
|
|
|
|
Revision ID: 20260826_0018
|
|
Revises: 20260826_0017
|
|
"""
|
|
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
|
|
from alembic import op
|
|
|
|
revision: str = "20260826_0018"
|
|
down_revision: str | None = "20260826_0017"
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
def id_column() -> sa.Column[object]:
|
|
return sa.Column("id", sa.Uuid(), primary_key=True, nullable=False)
|
|
|
|
|
|
def created_at() -> sa.Column[object]:
|
|
return sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now())
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"lifecycle_subjects",
|
|
id_column(),
|
|
sa.Column("target_type", sa.String(48), nullable=False),
|
|
sa.Column("target_ref", sa.String(255), nullable=False),
|
|
sa.Column("environment", sa.String(32), nullable=False),
|
|
sa.Column("state", sa.String(48), nullable=False),
|
|
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
|
|
sa.Column("superseded_by_ref", sa.String(255)),
|
|
created_at(),
|
|
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
sa.CheckConstraint("version >= 1", name="ck_lifecycle_subject_version"),
|
|
sa.UniqueConstraint(
|
|
"target_type", "target_ref", "environment", name="uq_lifecycle_subject_target"
|
|
),
|
|
)
|
|
op.create_index("ix_lifecycle_subjects_target_type", "lifecycle_subjects", ["target_type"])
|
|
op.create_index("ix_lifecycle_subjects_target_ref", "lifecycle_subjects", ["target_ref"])
|
|
op.create_index("ix_lifecycle_subjects_environment", "lifecycle_subjects", ["environment"])
|
|
op.create_index("ix_lifecycle_subjects_state", "lifecycle_subjects", ["state"])
|
|
|
|
op.create_table(
|
|
"lifecycle_policy_revisions",
|
|
id_column(),
|
|
sa.Column("key", sa.String(128), nullable=False),
|
|
sa.Column("revision", sa.Integer(), nullable=False),
|
|
sa.Column("scope", sa.String(48), nullable=False),
|
|
sa.Column("requirements", sa.JSON(), nullable=False),
|
|
sa.Column("fingerprint", sa.String(64), nullable=False),
|
|
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
|
|
sa.Column("created_by", sa.String(255), nullable=False),
|
|
created_at(),
|
|
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
sa.UniqueConstraint("key", "revision", name="uq_lifecycle_policy_revision"),
|
|
sa.UniqueConstraint("fingerprint", name="uq_lifecycle_policy_fingerprint"),
|
|
)
|
|
op.create_index("ix_lifecycle_policy_revisions_key", "lifecycle_policy_revisions", ["key"])
|
|
op.create_index("ix_lifecycle_policy_revisions_scope", "lifecycle_policy_revisions", ["scope"])
|
|
op.create_index(
|
|
"ix_lifecycle_policy_revisions_active", "lifecycle_policy_revisions", ["active"]
|
|
)
|
|
|
|
op.create_table(
|
|
"lifecycle_approval_requests",
|
|
id_column(),
|
|
sa.Column(
|
|
"policy_revision_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_policy_revisions.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"subject_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_subjects.id", ondelete="RESTRICT"),
|
|
),
|
|
sa.Column("target_type", sa.String(48), nullable=False),
|
|
sa.Column("target_ref", sa.String(255), nullable=False),
|
|
sa.Column("environment", sa.String(32), nullable=False),
|
|
sa.Column("requested_transition", sa.String(48), nullable=False),
|
|
sa.Column("evidence_snapshot", sa.JSON(), nullable=False),
|
|
sa.Column("evidence_fingerprint", sa.String(64), nullable=False),
|
|
sa.Column("status", sa.String(32), nullable=False),
|
|
sa.Column("blockers", sa.JSON(), nullable=False),
|
|
sa.Column("warnings", sa.JSON(), nullable=False),
|
|
sa.Column("requested_by", sa.String(255), nullable=False),
|
|
sa.Column("approved_by", sa.String(255)),
|
|
sa.Column("reason", sa.Text(), nullable=False),
|
|
sa.Column("expires_at", sa.DateTime(timezone=True)),
|
|
sa.Column("stale_at", sa.DateTime(timezone=True)),
|
|
sa.Column("decided_at", sa.DateTime(timezone=True)),
|
|
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
|
|
created_at(),
|
|
sa.CheckConstraint("version >= 1", name="ck_lifecycle_approval_version"),
|
|
)
|
|
for column in (
|
|
"policy_revision_id",
|
|
"subject_id",
|
|
"target_type",
|
|
"target_ref",
|
|
"environment",
|
|
"evidence_fingerprint",
|
|
"status",
|
|
"expires_at",
|
|
"created_at",
|
|
):
|
|
op.create_index(
|
|
f"ix_lifecycle_approval_requests_{column}", "lifecycle_approval_requests", [column]
|
|
)
|
|
|
|
op.create_table(
|
|
"lifecycle_approval_evidence",
|
|
sa.Column(
|
|
"approval_request_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_approval_requests.id", ondelete="RESTRICT"),
|
|
primary_key=True,
|
|
),
|
|
sa.Column("evidence_type", sa.String(64), primary_key=True),
|
|
sa.Column("evidence_ref", sa.String(255), primary_key=True),
|
|
sa.Column("evidence_digest", sa.String(64), nullable=False),
|
|
)
|
|
op.create_index(
|
|
"ix_lifecycle_approval_evidence_evidence_ref",
|
|
"lifecycle_approval_evidence",
|
|
["evidence_ref"],
|
|
)
|
|
|
|
op.create_table(
|
|
"lifecycle_promotion_plans",
|
|
id_column(),
|
|
sa.Column(
|
|
"approval_request_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_approval_requests.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"subject_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_subjects.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("current_state", sa.String(48), nullable=False),
|
|
sa.Column("desired_state", sa.String(48), nullable=False),
|
|
sa.Column("migration_class", sa.String(32), nullable=False),
|
|
sa.Column(
|
|
"candidate_deployment_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("capability_deployments.id", ondelete="RESTRICT"),
|
|
),
|
|
sa.Column("rollback_target_ref", sa.String(255), nullable=False),
|
|
sa.Column("project_consumers", sa.JSON(), nullable=False),
|
|
sa.Column("affected_identities", sa.JSON(), nullable=False),
|
|
sa.Column("impact_analysis", sa.JSON(), nullable=False),
|
|
sa.Column(
|
|
"migration_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("embedding_migrations.id", ondelete="RESTRICT"),
|
|
),
|
|
sa.Column("canary_strategy", sa.JSON(), nullable=False),
|
|
sa.Column("drain_strategy", sa.JSON(), nullable=False),
|
|
sa.Column("health_gates", sa.JSON(), nullable=False),
|
|
sa.Column("automatic_abort_conditions", sa.JSON(), nullable=False),
|
|
sa.Column("plan_fingerprint", sa.String(64), nullable=False),
|
|
sa.Column("status", sa.String(32), nullable=False, server_default="DRAFT"),
|
|
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
|
|
sa.Column("created_by", sa.String(255), nullable=False),
|
|
sa.Column("approved_by", sa.String(255)),
|
|
sa.Column("immutable_at", sa.DateTime(timezone=True)),
|
|
created_at(),
|
|
sa.CheckConstraint("version >= 1", name="ck_lifecycle_plan_version"),
|
|
sa.UniqueConstraint("plan_fingerprint", name="uq_lifecycle_plan_fingerprint"),
|
|
)
|
|
for column in (
|
|
"approval_request_id",
|
|
"subject_id",
|
|
"migration_class",
|
|
"candidate_deployment_id",
|
|
"migration_id",
|
|
"status",
|
|
"created_at",
|
|
):
|
|
op.create_index(
|
|
f"ix_lifecycle_promotion_plans_{column}", "lifecycle_promotion_plans", [column]
|
|
)
|
|
|
|
op.create_table(
|
|
"lifecycle_rollback_snapshots",
|
|
id_column(),
|
|
sa.Column(
|
|
"promotion_plan_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_promotion_plans.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("snapshot", sa.JSON(), nullable=False),
|
|
sa.Column("snapshot_digest", sa.String(64), nullable=False),
|
|
created_at(),
|
|
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
sa.UniqueConstraint("promotion_plan_id", name="uq_lifecycle_rollback_plan"),
|
|
sa.UniqueConstraint("snapshot_digest", name="uq_lifecycle_rollback_digest"),
|
|
)
|
|
op.create_index(
|
|
"ix_lifecycle_rollback_snapshots_promotion_plan_id",
|
|
"lifecycle_rollback_snapshots",
|
|
["promotion_plan_id"],
|
|
)
|
|
|
|
op.create_table(
|
|
"lifecycle_operations",
|
|
id_column(),
|
|
sa.Column(
|
|
"promotion_plan_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_promotion_plans.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("stage", sa.String(32), nullable=False),
|
|
sa.Column("idempotency_key", sa.String(128), nullable=False),
|
|
sa.Column("expected_subject_version", sa.Integer(), nullable=False),
|
|
sa.Column("requester", sa.String(255), nullable=False),
|
|
sa.Column("approver", sa.String(255), nullable=False),
|
|
sa.Column("executor", sa.String(255), nullable=False),
|
|
sa.Column("failure_code", sa.String(64)),
|
|
sa.Column("failure_details", sa.JSON(), nullable=False),
|
|
sa.Column("rollback_duration_ms", sa.Float()),
|
|
sa.Column("version", sa.Integer(), nullable=False, server_default="1"),
|
|
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
sa.Column("finished_at", sa.DateTime(timezone=True)),
|
|
sa.CheckConstraint("version >= 1", name="ck_lifecycle_operation_version"),
|
|
sa.UniqueConstraint("idempotency_key", name="uq_lifecycle_operation_idempotency"),
|
|
)
|
|
op.create_index(
|
|
"ix_lifecycle_operations_promotion_plan_id", "lifecycle_operations", ["promotion_plan_id"]
|
|
)
|
|
op.create_index("ix_lifecycle_operations_stage", "lifecycle_operations", ["stage"])
|
|
|
|
op.create_table(
|
|
"lifecycle_canary_runs",
|
|
id_column(),
|
|
sa.Column(
|
|
"operation_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_operations.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("mode", sa.String(48), nullable=False),
|
|
sa.Column("traffic_percent", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("eligible_clients", sa.JSON(), nullable=False),
|
|
sa.Column("eligible_projects", sa.JSON(), nullable=False),
|
|
sa.Column("target_request_count", sa.Integer(), nullable=False),
|
|
sa.Column("thresholds", sa.JSON(), nullable=False),
|
|
sa.Column("status", sa.String(48), nullable=False),
|
|
sa.Column("request_count", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("error_count", sa.Integer(), nullable=False, server_default="0"),
|
|
sa.Column("latency_p95_ms", sa.Float()),
|
|
sa.Column("abort_trigger", sa.String(64)),
|
|
sa.Column("result", sa.JSON(), nullable=False),
|
|
sa.Column("started_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
sa.Column("finished_at", sa.DateTime(timezone=True)),
|
|
sa.UniqueConstraint("operation_id", name="uq_lifecycle_canary_operation"),
|
|
)
|
|
op.create_index(
|
|
"ix_lifecycle_canary_runs_operation_id", "lifecycle_canary_runs", ["operation_id"]
|
|
)
|
|
op.create_index("ix_lifecycle_canary_runs_status", "lifecycle_canary_runs", ["status"])
|
|
|
|
op.create_table(
|
|
"retention_policy_revisions",
|
|
id_column(),
|
|
sa.Column("key", sa.String(128), nullable=False),
|
|
sa.Column("revision", sa.Integer(), nullable=False),
|
|
sa.Column("minimum_rollback_days", sa.Integer(), nullable=False),
|
|
sa.Column("requirements", sa.JSON(), nullable=False),
|
|
sa.Column("fingerprint", sa.String(64), nullable=False),
|
|
sa.Column("active", sa.Boolean(), nullable=False, server_default=sa.true()),
|
|
sa.Column("created_by", sa.String(255), nullable=False),
|
|
created_at(),
|
|
sa.Column("immutable_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
sa.UniqueConstraint("key", "revision", name="uq_retention_policy_revision"),
|
|
sa.UniqueConstraint("fingerprint", name="uq_retention_policy_fingerprint"),
|
|
)
|
|
op.create_index("ix_retention_policy_revisions_key", "retention_policy_revisions", ["key"])
|
|
op.create_index(
|
|
"ix_retention_policy_revisions_active", "retention_policy_revisions", ["active"]
|
|
)
|
|
|
|
op.create_table(
|
|
"lifecycle_retention_records",
|
|
id_column(),
|
|
sa.Column(
|
|
"policy_revision_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("retention_policy_revisions.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("target_type", sa.String(48), nullable=False),
|
|
sa.Column("target_ref", sa.String(255), nullable=False),
|
|
sa.Column("state", sa.String(32), nullable=False),
|
|
sa.Column("retained_until", sa.DateTime(timezone=True)),
|
|
sa.Column("legal_hold", sa.Boolean(), nullable=False, server_default=sa.false()),
|
|
sa.Column("reason", sa.Text(), nullable=False),
|
|
created_at(),
|
|
sa.UniqueConstraint("target_type", "target_ref", name="uq_lifecycle_retention_target"),
|
|
)
|
|
for column in ("policy_revision_id", "target_type", "target_ref", "state", "retained_until"):
|
|
op.create_index(
|
|
f"ix_lifecycle_retention_records_{column}", "lifecycle_retention_records", [column]
|
|
)
|
|
|
|
op.create_table(
|
|
"lifecycle_cleanup_plans",
|
|
id_column(),
|
|
sa.Column("target_type", sa.String(48), nullable=False),
|
|
sa.Column("target_ref", sa.String(255), nullable=False),
|
|
sa.Column("action", sa.String(48), nullable=False),
|
|
sa.Column("dependencies", sa.JSON(), nullable=False),
|
|
sa.Column("dependency_digest", sa.String(64), nullable=False),
|
|
sa.Column("reclaimable_bytes", sa.BigInteger(), nullable=False),
|
|
sa.Column("retention_state", sa.String(32), nullable=False),
|
|
sa.Column("blockers", sa.JSON(), nullable=False),
|
|
sa.Column("status", sa.String(32), nullable=False),
|
|
sa.Column("created_by", sa.String(255), nullable=False),
|
|
created_at(),
|
|
sa.Column("executed_at", sa.DateTime(timezone=True)),
|
|
)
|
|
for column in ("target_type", "target_ref", "status", "created_at"):
|
|
op.create_index(f"ix_lifecycle_cleanup_plans_{column}", "lifecycle_cleanup_plans", [column])
|
|
|
|
op.create_table(
|
|
"artifact_location_removal_records",
|
|
id_column(),
|
|
sa.Column(
|
|
"artifact_location_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("artifact_locations.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column(
|
|
"cleanup_plan_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_cleanup_plans.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("prior_status", sa.String(32), nullable=False),
|
|
sa.Column("prior_observed_sha256", sa.String(64)),
|
|
sa.Column("prior_size_bytes", sa.BigInteger()),
|
|
sa.Column("removed_by", sa.String(255), nullable=False),
|
|
sa.Column("removed_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
)
|
|
op.create_index(
|
|
"ix_artifact_location_removal_records_artifact_location_id",
|
|
"artifact_location_removal_records",
|
|
["artifact_location_id"],
|
|
)
|
|
op.create_index(
|
|
"ix_artifact_location_removal_records_cleanup_plan_id",
|
|
"artifact_location_removal_records",
|
|
["cleanup_plan_id"],
|
|
)
|
|
|
|
op.create_table(
|
|
"lifecycle_events",
|
|
id_column(),
|
|
sa.Column("event_type", sa.String(64), nullable=False),
|
|
sa.Column("object_type", sa.String(48), nullable=False),
|
|
sa.Column("object_ref", sa.String(255), nullable=False),
|
|
sa.Column("from_state", sa.String(48)),
|
|
sa.Column("to_state", sa.String(48)),
|
|
sa.Column("actor", sa.String(255), nullable=False),
|
|
sa.Column("actor_role", sa.String(32), nullable=False),
|
|
sa.Column(
|
|
"policy_revision_id",
|
|
sa.Uuid(),
|
|
sa.ForeignKey("lifecycle_policy_revisions.id", ondelete="RESTRICT"),
|
|
),
|
|
sa.Column("evidence_ids", sa.JSON(), nullable=False),
|
|
sa.Column("reason", sa.Text(), nullable=False),
|
|
sa.Column("change_id", sa.String(64), nullable=False),
|
|
sa.Column("details", sa.JSON(), nullable=False),
|
|
sa.Column("occurred_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
|
)
|
|
for column in (
|
|
"event_type",
|
|
"object_type",
|
|
"object_ref",
|
|
"policy_revision_id",
|
|
"change_id",
|
|
"occurred_at",
|
|
):
|
|
op.create_index(f"ix_lifecycle_events_{column}", "lifecycle_events", [column])
|
|
|
|
op.create_index(
|
|
"uq_capability_deployment_one_production_stable",
|
|
"capability_deployments",
|
|
["capability_contract_id"],
|
|
unique=True,
|
|
postgresql_where=sa.text("production = true AND status = 'stable'"),
|
|
sqlite_where=sa.text("production = 1 AND status = 'stable'"),
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index(
|
|
"uq_capability_deployment_one_production_stable",
|
|
table_name="capability_deployments",
|
|
)
|
|
for table in (
|
|
"lifecycle_events",
|
|
"artifact_location_removal_records",
|
|
"lifecycle_cleanup_plans",
|
|
"lifecycle_retention_records",
|
|
"retention_policy_revisions",
|
|
"lifecycle_canary_runs",
|
|
"lifecycle_operations",
|
|
"lifecycle_rollback_snapshots",
|
|
"lifecycle_promotion_plans",
|
|
"lifecycle_approval_evidence",
|
|
"lifecycle_approval_requests",
|
|
"lifecycle_policy_revisions",
|
|
"lifecycle_subjects",
|
|
):
|
|
op.drop_table(table)
|