76 lines
3.1 KiB
Python
76 lines
3.1 KiB
Python
from __future__ import annotations
|
|
|
|
from .enums import MigrationStatus, ModelLifecycle, UpgradeClass, VerificationStatus
|
|
|
|
|
|
class InvalidTransition(ValueError):
|
|
pass
|
|
|
|
|
|
MODEL_TRANSITIONS: dict[ModelLifecycle, frozenset[ModelLifecycle]] = {
|
|
ModelLifecycle.DISCOVERED: frozenset(
|
|
{ModelLifecycle.CANDIDATE, ModelLifecycle.REJECTED, ModelLifecycle.DEPRECATED}
|
|
),
|
|
ModelLifecycle.CANDIDATE: frozenset(
|
|
{ModelLifecycle.DOWNLOADING, ModelLifecycle.REJECTED, ModelLifecycle.DEPRECATED}
|
|
),
|
|
ModelLifecycle.DOWNLOADING: frozenset({ModelLifecycle.QUARANTINED, ModelLifecycle.REJECTED}),
|
|
ModelLifecycle.QUARANTINED: frozenset(
|
|
{ModelLifecycle.VERIFIED, ModelLifecycle.SECURITY_BLOCKED, ModelLifecycle.LICENSE_BLOCKED}
|
|
),
|
|
ModelLifecycle.VERIFIED: frozenset({ModelLifecycle.TESTING, ModelLifecycle.INCOMPATIBLE}),
|
|
ModelLifecycle.TESTING: frozenset(
|
|
{ModelLifecycle.APPROVED, ModelLifecycle.INCOMPATIBLE, ModelLifecycle.REJECTED}
|
|
),
|
|
ModelLifecycle.APPROVED: frozenset(
|
|
{ModelLifecycle.STANDBY, ModelLifecycle.ACTIVE, ModelLifecycle.DEPRECATED}
|
|
),
|
|
ModelLifecycle.STANDBY: frozenset({ModelLifecycle.ACTIVE, ModelLifecycle.DEPRECATED}),
|
|
ModelLifecycle.ACTIVE: frozenset({ModelLifecycle.STANDBY, ModelLifecycle.DEPRECATED}),
|
|
ModelLifecycle.DEPRECATED: frozenset({ModelLifecycle.ARCHIVED, ModelLifecycle.ACTIVE}),
|
|
ModelLifecycle.ARCHIVED: frozenset(),
|
|
ModelLifecycle.REJECTED: frozenset(),
|
|
ModelLifecycle.INCOMPATIBLE: frozenset({ModelLifecycle.CANDIDATE}),
|
|
ModelLifecycle.SECURITY_BLOCKED: frozenset(
|
|
{ModelLifecycle.QUARANTINED, ModelLifecycle.REJECTED}
|
|
),
|
|
ModelLifecycle.LICENSE_BLOCKED: frozenset(
|
|
{ModelLifecycle.QUARANTINED, ModelLifecycle.REJECTED}
|
|
),
|
|
}
|
|
|
|
|
|
def assert_model_transition(current: ModelLifecycle, target: ModelLifecycle) -> None:
|
|
if target not in MODEL_TRANSITIONS[current]:
|
|
raise InvalidTransition(f"model lifecycle transition {current} -> {target} is not allowed")
|
|
|
|
|
|
def assert_promotion_allowed(
|
|
*,
|
|
upgrade_class: UpgradeClass,
|
|
verification_status: VerificationStatus,
|
|
local_benchmark_ids: list[str],
|
|
project_benchmark_ids: list[str],
|
|
operator_approval_id: str | None,
|
|
rollback_deployment_id: str | None,
|
|
migration_status: MigrationStatus | None = None,
|
|
) -> None:
|
|
blockers: list[str] = []
|
|
if verification_status is not VerificationStatus.VERIFIED:
|
|
blockers.append("artifact is not verified")
|
|
if not local_benchmark_ids:
|
|
blockers.append("local benchmark evidence is missing")
|
|
if not project_benchmark_ids:
|
|
blockers.append("project benchmark evidence is missing")
|
|
if not operator_approval_id:
|
|
blockers.append("operator approval is missing")
|
|
if not rollback_deployment_id:
|
|
blockers.append("rollback target is missing")
|
|
if (
|
|
upgrade_class is UpgradeClass.REQUIRES_REINDEX
|
|
and migration_status is not MigrationStatus.READY
|
|
):
|
|
blockers.append("ready migration is required for reindexing change")
|
|
if blockers:
|
|
raise InvalidTransition("promotion blocked: " + "; ".join(blockers))
|