Files
aios/tests/autonomous_release/test_release_gates.py
T

96 lines
3.6 KiB
Python

from dataclasses import replace
from datetime import datetime, timezone
import pytest
from aios_autonomous_release_candidate.approval import ApprovalVerificationResult
from aios_autonomous_release_candidate.errors import ReleaseBlocked
from aios_autonomous_release_candidate.models import ReleasePlan
from aios_autonomous_release_candidate.release import ReleaseController, ReleaseState
from aios_autonomous_release_candidate.release_gate import ReleaseEvidence, ReleaseGate
from aios_autonomous_release_candidate.tag import TagReference, TagVerifier
OID = "a" * 40
TAG_OID = "b" * 40
DIGEST = "c" * 64
def plan() -> ReleasePlan:
return ReleasePlan(
task_id="release-task",
repository="aios/aios",
branch="integration/autonomous-release",
version="V2R1C4",
target_oid=OID,
tree_oid="d" * 40,
artifact_sha256=DIGEST,
manifest_sha256="e" * 64,
package_closure_sha256="3" * 64,
evidence_closure_oid="f" * 40,
approval_plan_id="owner-release-v2r1c4",
rollback_approval_plan_id="owner-rollback-v2r1c4",
rollback_artifact_sha256="1" * 64,
expected_tag_oid=TAG_OID,
expected_target_oid=OID,
production_config_sha256="2" * 64,
)
def approval(plan_id: str) -> ApprovalVerificationResult:
return ApprovalVerificationResult(
approval_plan_id=plan_id,
task_id="release-task",
signing_key_id="owner-kms-1",
verified_at=datetime(2026, 7, 26, tzinfo=timezone.utc),
)
def test_deploy_and_rollback_require_distinct_verified_approvals() -> None:
controller = ReleaseController()
controller.attach_plan(plan())
controller.mark_built()
controller.verify(artifact_ok=True, evidence_ok=True)
controller.request_owner_approval()
with pytest.raises(ReleaseBlocked, match="OWNER_APPROVAL_PLAN_MISMATCH"):
controller.approve(approval("wrong-plan"))
controller.approve(approval("owner-release-v2r1c4"))
controller.start_deployment()
controller.mark_deployed()
controller.request_rollback("candidate acceptance regression")
with pytest.raises(ReleaseBlocked, match="ROLLBACK_APPROVAL_PLAN_MISMATCH"):
controller.approve_rollback(approval("owner-release-v2r1c4"))
controller.approve_rollback(approval("owner-rollback-v2r1c4"))
controller.start_rollback()
assert controller.mark_rolled_back().state == ReleaseState.ROLLED_BACK
def test_annotated_tag_uses_object_and_peeled_oids_separately() -> None:
tag = TagReference("refs/tags/V2R1C4", "tag", TAG_OID, OID)
TagVerifier().verify(tag, expected_tag_oid=TAG_OID, expected_target_oid=OID)
with pytest.raises(ReleaseBlocked, match="TAG_PEELED_TARGET_MISMATCH"):
TagVerifier().verify(tag, expected_tag_oid=TAG_OID, expected_target_oid="3" * 40)
def test_release_gate_requires_four_source_tag_and_artifact_closure() -> None:
evidence = ReleaseEvidence(
expected_version="V2R1C4",
registry_version="V2R1C4",
baseline_version="V2R1C4",
local_tag_oid=TAG_OID,
remote_tag_oid=TAG_OID,
gitea_tag_oid=TAG_OID,
expected_tag_oid=TAG_OID,
local_peeled_target_oid=OID,
remote_peeled_target_oid=OID,
gitea_peeled_target_oid=OID,
expected_target_oid=OID,
artifact_sha256=DIGEST,
manifest_sha256="e" * 64,
evidence_sha256="4" * 64,
rollback_artifact_sha256="1" * 64,
)
ReleaseGate().verify(evidence)
with pytest.raises(ReleaseBlocked, match="TAG_GITEA_MISMATCH"):
ReleaseGate().verify(replace(evidence, gitea_tag_oid="5" * 40))