Add lifecycle registration and revisions with atomic packet uploads

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
tegwick 2026-10-04 19:31:39 +02:00
parent 7ce1e589f7
commit ee472acc87
10 changed files with 332 additions and 35 deletions

View file

@ -87,6 +87,85 @@ def test_body_limits_and_idempotency_conflict(api):
assert call(api, path, method="POST", body={"version": 1, "body": {"text": "second"}})[0] == 409
def test_unimplemented_binding_and_creation_routes_are_unavailable(api):
assert call(api, "/v1/decisions", method="POST", body={})[0] == 404
def test_unimplemented_binding_route_is_unavailable(api):
assert call(api, "/v1/decisions", method="POST", body={})[0] == 400
assert call(api, "/v1/decisions/memo-1/accept", method="POST", body={})[0] == 404
def packet_command(api, ident="new-record"):
import base64
import hashlib
memo = call(api, "/v1/decisions/memo-1")[1]
content = b"New decision supporting evidence"
digest = "sha256:" + hashlib.sha256(content).hexdigest()
memo.update(id=ident, packet=[{"item_id": "doc", "label": "Evidence", "hash": digest}])
return {"memo": memo, "documents": [{"digest": digest, "media_type": "text/plain",
"content_base64": base64.b64encode(content).decode()}]}
def test_register_and_revise_packet_through_api_without_operator_load(api):
command = packet_command(api)
status, receipt, _ = call(api, "/v1/decisions", method="POST", body=command)
assert status == 200 and receipt["resource"] == "/v1/decisions/new-record?version=1"
assert call(api, "/v1/decisions", method="POST", body=command)[1] == receipt
status, stored, _ = call(api, "/v1/decisions/new-record")
assert status == 200 and stored == command["memo"]
stored.update(version=2, brief="Revised assessment")
revision = {"memo": stored, "expected_version": 1, "summary": "Updated assessment"}
status, receipt2, _ = call(api, "/v1/decisions/new-record/revisions", method="POST", body=revision)
assert status == 200 and receipt2["version"] == 2
assert call(api, "/v1/decisions/new-record/revisions", method="POST", body=revision)[1] == receipt2
assert call(api, "/v1/decisions/new-record", query="version=1")[1]["brief"] != "Revised assessment"
assert call(api, "/v1/decisions/new-record/revisions", method="POST", body=revision,
HTTP_IDEMPOTENCY_KEY="different-writer")[0] == 409
@pytest.mark.parametrize("mutate", [
lambda c: c["memo"].update(version=True),
lambda c: c["memo"].update(sealed="false"),
lambda c: c["memo"].update(unknown="not allowed"),
lambda c: c["memo"]["binding"]["target"].update(requires_new_bind="false"),
lambda c: c["memo"]["binding"]["principal"].update(identity_verified=True),
lambda c: c["memo"]["packet"].append(dict(c["memo"]["packet"][0])),
lambda c: c["documents"][0].update(content_base64="not base64"),
lambda c: c["documents"][0].update(content_base64="bm90IHRoZSBzYW1l"),
lambda c: c["memo"].update(approval_id="invented"),
])
def test_closed_memo_and_content_validation(api, mutate):
command = packet_command(api)
mutate(command)
assert call(api, "/v1/decisions", method="POST", body=command)[0] == 400
assert api.service.repository.store.lifecycle_counts()["records"] == 1
def test_known_digest_alone_does_not_grant_custody_of_other_record(api):
command = packet_command(api)
assert call(api, "/v1/decisions", method="POST", body=command)[0] == 200
command["memo"]["id"] = "another-record"
command.pop("documents")
assert call(api, "/v1/decisions", method="POST", body=command,
HTTP_IDEMPOTENCY_KEY="another")[0] == 404
def test_packet_write_rolls_back_when_event_queue_fails(api):
command = packet_command(api)
store = api.service.repository.store
with store._connection() as db:
db.execute("CREATE TRIGGER fail_packet_event BEFORE INSERT ON lifecycle_pending "
"BEGIN SELECT RAISE(ABORT,'injected'); END")
assert call(api, "/v1/decisions", method="POST", body=command)[0] == 503
with store._connection() as db:
assert db.execute("SELECT COUNT(*) FROM documents").fetchone()[0] == 0
assert store.lifecycle_counts()["records"] == 1
@pytest.mark.parametrize("act,step,level", [("login", "acknowledge", "acknowledgment"),
("approve", "approve", "organizational")])
def test_shared_command_decoder_preserves_l0_and_l3_binding_fields(api, act, step, level):
command = packet_command(api)
command["memo"].update(requested_act=act, step_kind=step, binding_level=level)
command["memo"]["binding"]["target"]["requires_new_bind"] = True
assert call(api, "/v1/decisions", method="POST", body=command)[0] == 200
result = call(api, "/v1/decisions/new-record")[1]
assert result["binding"]["target"]["requires_new_bind"] is True
assert result["requested_act"] == act and result["binding_level"] == level