autonomy-smoke-tests pr82 PASS evidence_sha256=c16c68f86ee0c4e8cfd0af3f76078be45441ee151395fbde5800145c08e189b5
autonomy-unit-tests pr82 PASS evidence_sha256=168bb88573b44876966c64ec2dc2b9e030fc7635ca0b18538678f1c492c97ea2
campaign-approval-gate pr82 PASS evidence_sha256=0616d6aa11496391686dcbd658123e51d8d057e1f02661af7ef8512554e404d8
git-diff-check pr82 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile pr82 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract pr82 PASS evidence_sha256=8250e3e736db14432c421c46f995c201540e2b9c50958bdeb3ef7b057c4fd577
release-state-machine pr82 PASS evidence_sha256=b6c51b401d65a9c69435f8b86cecab752d65ded7188228aab3de3eb010990b60
secret-scan pr82 PASS evidence_sha256=f8ed9052d5ba0fbe8e52f171ea180fa353a89b4049cd3e1ed881736d0622c752
signer-validator-contract pr82 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
aios.owner-final-merge-authorization-v2 real v2 contract tests passed
118 lines
6.1 KiB
Python
118 lines
6.1 KiB
Python
from __future__ import annotations
|
|
import json
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
import sys
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT/"active-runtime"/"app"))
|
|
import release_approval_reopen as reopen
|
|
|
|
CID="rc-v2r2c2-4642b74af62c"; SRC="4"*40; TREE="a"*40
|
|
ART="5"*64; MAN="8"*64; PLAN="f"*64; POL="2"*64; FP="9"*64
|
|
NOW=datetime(2026,9,12,3,10,tzinfo=timezone.utc)
|
|
|
|
class Sagas:
|
|
def __init__(self, root): self.root=root
|
|
def compare_and_set(self, candidate_id, *, expected_states, target, event, details, expected_revision):
|
|
p=self.root/"sagas"/f"{candidate_id}.json"; x=json.loads(p.read_text())
|
|
if x["state"] not in expected_states: raise RuntimeError("CAS")
|
|
if int(x.get("revision",0)) != expected_revision: raise RuntimeError("SagaConflict")
|
|
x["state"]=target; x["revision"]=int(x.get("revision",0))+1
|
|
x.setdefault("events",[]).append({"event":event,"details":details,"timestamp":"2026-09-12T03:10:00Z"})
|
|
p.write_text(json.dumps(x,sort_keys=True)); return x
|
|
class Service:
|
|
def __init__(self, root): self.sagas=Sagas(root)
|
|
|
|
def w(p,o):
|
|
p.parent.mkdir(parents=True,exist_ok=True); p.write_text(json.dumps(o,sort_keys=True)); p.chmod(0o640)
|
|
|
|
_MISSING = object()
|
|
|
|
def state(tmp, expired=True, side_marker=_MISSING, saga="APPROVED", acceptance="ACCEPTED", extra=False):
|
|
r=tmp/"silent-release"
|
|
for d in ("candidates","sagas","release-plans","acceptance","approvals"):
|
|
(r/d).mkdir(parents=True,exist_ok=True,mode=0o750)
|
|
candidate={"candidate_id":CID,"state":"READY_FOR_APPROVAL","approval_state":"PENDING",
|
|
"source_commit":SRC,"tree_oid":TREE,"artifact_sha256":ART,"manifest_sha256":MAN}
|
|
if side_marker is not _MISSING:
|
|
candidate["side_effects_executed"]=side_marker
|
|
w(r/"candidates"/f"{CID}.json", candidate)
|
|
ev=[{"event":"saga_created","details":{}},{"event":"trusted_candidate_ready_for_approval","details":{}},
|
|
{"event":"signed_owner_approval_accepted","details":{"approval_fingerprint":FP}}]
|
|
if extra: ev.append({"event":"deployment_started","details":{}})
|
|
w(r/"sagas"/f"{CID}.json",{"candidate_id":CID,"state":saga,"revision":2,"journal_integrity":True,"events":ev})
|
|
w(r/"release-plans"/f"{CID}.json",{"sha256":PLAN,"policy_sha256":POL})
|
|
w(r/"acceptance"/f"{CID}.json",{"status":acceptance,"release_plan_sha256":PLAN})
|
|
w(r/"approvals"/f"{CID}.json",{"candidate_id":CID,"fingerprint":FP,"source_commit":SRC,"tree_oid":TREE,
|
|
"artifact_sha256":ART,"manifest_sha256":MAN,"release_plan_sha256":PLAN,"policy_sha256":POL,
|
|
"expires_at":"2026-09-12T02:59:05Z" if expired else "2026-09-12T04:59:05Z"})
|
|
return r
|
|
|
|
def req(key="reopen:test:0001"):
|
|
return {"schema":reopen.REQUEST_SCHEMA,"candidate_id":CID,"expected_approval_fingerprint":FP,
|
|
"expected_source_commit":SRC,"expected_tree_oid":TREE,"expected_artifact_sha256":ART,
|
|
"expected_manifest_sha256":MAN,"expected_release_plan_sha256":PLAN,"expected_policy_sha256":POL,
|
|
"reason":"expired approval after pre-side-effect infrastructure recovery","idempotency_key":key}
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def enum(monkeypatch): monkeypatch.setattr(reopen,"_state",lambda x:x)
|
|
|
|
def test_happy(tmp_path):
|
|
r=state(tmp_path); old=(r/"approvals"/f"{CID}.json").read_bytes()
|
|
out=reopen.reopen_owner_approval(Service(r),req(),state_root=r,now=NOW)
|
|
assert out["status"]=="REOPENED"; s=json.loads((r/"sagas"/f"{CID}.json").read_text())
|
|
assert s["state"]=="READY_FOR_APPROVAL" and s["events"][-1]["event"]==reopen.EVENT_NAME
|
|
assert not (r/"approvals"/f"{CID}.json").exists()
|
|
assert (r/out["history_ref"]).read_bytes()==old
|
|
|
|
def test_idempotent(tmp_path):
|
|
r=state(tmp_path); svc=Service(r); a=reopen.reopen_owner_approval(svc,req(),state_root=r,now=NOW)
|
|
b=reopen.reopen_owner_approval(svc,req(),state_root=r,now=NOW)
|
|
assert b["status"]=="IDEMPOTENT" and a["approval_cycle_id"]==b["approval_cycle_id"]
|
|
|
|
@pytest.mark.parametrize("kind",["unexpired","saga","acceptance","side","advanced"])
|
|
def test_rejections(tmp_path,kind):
|
|
kw={}
|
|
expected=""
|
|
if kind=="unexpired": kw["expired"]=False; expected="APPROVAL_REOPEN_APPROVAL_NOT_EXPIRED"
|
|
if kind=="saga": kw["saga"]="READY_FOR_APPROVAL"; expected="APPROVAL_REOPEN_SAGA_STATE_INVALID"
|
|
if kind=="acceptance": kw["acceptance"]="REJECTED"; expected="APPROVAL_REOPEN_BINDING_MISMATCH"
|
|
if kind=="side": kw["side_marker"]=True; expected="APPROVAL_REOPEN_SIDE_EFFECTS_ALREADY_EXECUTED"
|
|
if kind=="advanced": kw["extra"]=True; expected="APPROVAL_REOPEN_SAGA_ALREADY_ADVANCED"
|
|
r=state(tmp_path,**kw)
|
|
with pytest.raises(reopen.ApprovalReopenError,match=expected):
|
|
reopen.reopen_owner_approval(Service(r),req(),state_root=r,now=NOW)
|
|
|
|
def test_binding_drift(tmp_path):
|
|
r=state(tmp_path); q=req(); q["expected_artifact_sha256"]="6"*64
|
|
with pytest.raises(reopen.ApprovalReopenError,match="APPROVAL_REOPEN_BINDING_MISMATCH"):
|
|
reopen.reopen_owner_approval(Service(r),q,state_root=r,now=NOW)
|
|
|
|
def test_history_collision(tmp_path):
|
|
r=state(tmp_path); h=r/"approval-history"/CID/f"{FP}.json"; h.parent.mkdir(parents=True); h.write_text("{}")
|
|
with pytest.raises(reopen.ApprovalReopenError,match="APPROVAL_REOPEN_HISTORY_COLLISION"):
|
|
reopen.reopen_owner_approval(Service(r),req(),state_root=r,now=NOW)
|
|
|
|
|
|
def test_real_durable_schema_without_side_effect_field_is_supported(tmp_path):
|
|
r=state(tmp_path)
|
|
candidate=json.loads((r/"candidates"/f"{CID}.json").read_text())
|
|
assert "side_effects_executed" not in candidate
|
|
out=reopen.reopen_owner_approval(
|
|
Service(r), req("reopen:test:real-schema"), state_root=r, now=NOW
|
|
)
|
|
assert out["status"]=="REOPENED"
|
|
|
|
@pytest.mark.parametrize("value",[True,None,1,"false"])
|
|
def test_explicit_non_false_side_effect_marker_fails_closed(tmp_path,value):
|
|
r=state(tmp_path,side_marker=value)
|
|
with pytest.raises(
|
|
reopen.ApprovalReopenError,
|
|
match="APPROVAL_REOPEN_SIDE_EFFECTS_ALREADY_EXECUTED"
|
|
):
|
|
reopen.reopen_owner_approval(
|
|
Service(r), req("reopen:test:marker:bad"), state_root=r, now=NOW
|
|
)
|