Implement lifecycle workspace publication and audit delivery

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
This commit is contained in:
tegwick 2026-10-04 20:17:24 +02:00
parent ee472acc87
commit 5fec7de4cb
21 changed files with 1022 additions and 78 deletions

View file

@ -11,11 +11,12 @@ from urllib.parse import parse_qs
from .lifecycle_service import Caller, LifecycleError
from .lifecycle_commands import decode_memo
from .oidc import ORIGIN
from .records import dumps
from .store import Conflict, EvidenceUnavailable
MAX_BODY = 256 * 1024
ROUTE = re.compile(r"/v1/decisions/([A-Za-z0-9._:-]{1,256})(?:/(events|comments|sources|evaluations|revisions))?")
ROUTE = re.compile(r"/v1/decisions/([A-Za-z0-9._:-]{1,256})(?:/(events|comments|sources|evaluations|revisions|publications))?")
def _object(pairs):
@ -77,6 +78,11 @@ class LifecycleAPI:
params = parse_qs(query, keep_blank_values=True, strict_parsing=True, max_num_fields=4)
if any(len(value) != 1 for value in params.values()):
raise ValueError()
if method == "GET" and registration:
if set(params) - {"q", "cursor"}:
raise ValueError()
return 200, self.service.search(caller, query=params.get("q", [""])[0],
cursor=params.get("cursor", [None])[0])
if method == "GET" and resource is None:
if set(params) - {"version"}:
raise ValueError()
@ -90,7 +96,7 @@ class LifecycleAPI:
return 200, {"events": self.service.events(caller, memo_id,
after=int(params.get("after", ["0"])[0]), limit=int(params.get("limit", ["100"])[0]))}
kinds = {"comments": "comment", "sources": "source", "evaluations": "evaluation"}
if method != "POST" or resource not in {*kinds, "register", "revisions"}:
if method != "POST" or resource not in {*kinds, "register", "revisions", "publications"}:
raise LifecycleError(405, "method_not_allowed")
if params:
raise ValueError()
@ -109,6 +115,11 @@ class LifecycleAPI:
key = environ.get("HTTP_IDEMPOTENCY_KEY", "")
if not isinstance(key, str) or not key.strip() or len(key) > 256:
raise ValueError()
if resource == "publications":
if not isinstance(data, dict) or set(data) != {"version", "audience"}:
raise ValueError()
event_id = self.service.publish(caller, memo_id, data["version"], audience=data["audience"], key=key)
return 200, {"event_id": event_id, "memo_id": memo_id, "version": data["version"]}
if resource in {"register", "revisions"}:
required = {"memo"} if registration else {"memo", "expected_version", "summary"}
if (not isinstance(data, dict) or not required <= data.keys()
@ -124,7 +135,8 @@ class LifecycleAPI:
event_id = self.service.revise(caller, memo, key=key,
expected_version=data["expected_version"], summary=data["summary"], documents=documents)
return 200, {"event_id": event_id, "memo_id": memo.id, "version": memo.version,
"resource": f"/v1/decisions/{memo.id}?version={memo.version}"}
"resource": f"/v1/decisions/{memo.id}?version={memo.version}",
"workspace_url": f"{ORIGIN}/decisions/{memo.id}?version={memo.version}"}
if not isinstance(data, dict) or set(data) != {"version", "body"}:
raise ValueError()
event_id = self.service.annotate(caller, memo_id, data["version"], kind=kinds[resource],