Let supersede() reissue an expired approval given a fresh window
expired stays a terminal exit from valid (state machine unchanged), but supersede() now also accepts it as a source status when the caller supplies a validity window, linking a new successor approval to the expired original instead of requiring a caller to mint an unlinked replacement from scratch. Without a fresh window, an expired source still conflicts exactly as before. The original's status becomes superseded (not left as a bare dead-end expired) and superseded_by points at the successor, so the link is documented rather than hidden -- informed-decision's overview already renders this automatically, no changes needed there. Prompted by the operator missing infd-20260927-b01's approval window during today's key-cape login outage (APPROVAL-WP-0003). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: sonnet Assistant-Process: 169987@bnt-lap001 Assistant-Session: 322ef1ef-9048-4021-8570-b6d6f6347999
This commit is contained in:
parent
71ae259877
commit
c93628a017
6 changed files with 294 additions and 7 deletions
|
|
@ -125,6 +125,44 @@ def test_claim_after_revoke(app):
|
|||
assert claim["reason_code"] == "revoked"
|
||||
|
||||
|
||||
def test_supersede_endpoint_reissues_an_expired_approval(app):
|
||||
expired = {
|
||||
"not_before": "2026-08-29T09:00:00+00:00",
|
||||
"expires_at": "2026-08-29T10:00:00+00:00", # before the frozen test clock
|
||||
}
|
||||
_, created = call(app, "POST", "/v1/approvals", {"binding": binding(), "validity": expired})
|
||||
aid = created["id"]
|
||||
status, fetched = call(app, "GET", f"/v1/approvals/{aid}")
|
||||
assert status == 200
|
||||
assert fetched["status"] == "expired"
|
||||
|
||||
fresh = {"not_before": "2026-08-29T13:00:00+00:00", "expires_at": "2026-08-29T18:00:00+00:00"}
|
||||
status, result = call(app, "POST", f"/v1/approvals/{aid}/supersede", {"validity": fresh})
|
||||
assert status == 200
|
||||
successor_id = result["successor_id"]
|
||||
|
||||
status, original = call(app, "GET", f"/v1/approvals/{aid}")
|
||||
assert original["status"] == "superseded"
|
||||
assert original["superseded_by"] == successor_id
|
||||
|
||||
status, successor = call(app, "GET", f"/v1/approvals/{successor_id}")
|
||||
assert successor["status"] == "requested"
|
||||
assert successor["validity"] == fresh
|
||||
|
||||
|
||||
def test_supersede_endpoint_refuses_expired_without_validity(app):
|
||||
expired = {
|
||||
"not_before": "2026-08-29T09:00:00+00:00",
|
||||
"expires_at": "2026-08-29T10:00:00+00:00",
|
||||
}
|
||||
_, created = call(app, "POST", "/v1/approvals", {"binding": binding(), "validity": expired})
|
||||
aid = created["id"]
|
||||
call(app, "GET", f"/v1/approvals/{aid}") # persist the expiry
|
||||
status, body = call(app, "POST", f"/v1/approvals/{aid}/supersede", {})
|
||||
assert status == 409
|
||||
assert body["error"] == "conflict"
|
||||
|
||||
|
||||
def test_store_unavailable_is_503():
|
||||
from approval_engine.api import App
|
||||
from approval_engine.auth import Identity, StaticTokenAuthenticator
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue