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

@ -1,4 +1,4 @@
# Lifecycle HTTP adapter — initial subset
# Lifecycle HTTP adapter — registration, revisions and collaboration
2026-10-04; INFD-WP-0007-T04, in progress. `LifecycleAPI` is an unmounted WSGI
adapter over `LifecycleService`. It starts no listener and adds no route to the
@ -6,6 +6,8 @@ existing browser app or public ingress.
| Method and path | Input | Result |
| --- | --- | --- |
| `POST /v1/decisions` | `memo`, optional `documents` | Register an unbound version-one memo and return event/record/revision/resource receipt |
| `POST /v1/decisions/{id}/revisions` | `memo`, `expected_version`, `summary`, optional `documents` | Compare-and-append successor revision; same receipt shape |
| `GET /v1/decisions/{id}` | Optional positive integer `version` | Existing Memo JSON for the current or specified revision |
| `GET /v1/decisions/{id}/events` | Optional `after` sequence cursor, `limit` 1–100 | `{"events": [...]}` ordered by sequence; use last returned sequence for next page |
| `POST /v1/decisions/{id}/comments` | `{"version": 1, "body": {"text": "...", "locator": "optional"}}` | Durable event receipt |
@ -17,8 +19,7 @@ Ids are 1–256 ASCII letters/digits or `.`, `_`, `:`, `-`. POST requires exact
nonempty `Idempotency-Key` up to 256 characters. Mutation receipts are HTTP 200
for both first submission and replay, with `event_id`, `memo_id`, `version`.
Unknown fields, duplicate JSON/query keys, non-finite JSON values, boolean
revision numbers and truncated bodies are rejected. Version-one registration,
revision, search, publication and disposition routes are not yet exposed.
revision numbers and truncated bodies are rejected. Search, publication and disposition routes are not yet exposed.
Authentication runs before routing or reading a mutation body. The composition
must explicitly supply a bearer-token verifier returning an authenticated
@ -39,9 +40,45 @@ identity, 403 policy denial, 404 unavailable record/route, 405 unsupported metho
service or unusable policy evidence. Raw exception text and credentials are never
returned. Upstream raw diagnostics are not forwarded.
T04 remains in progress: complete closed memo mutation schemas, canonical URLs,
search/publication contracts, admitted authenticators and runtime wiring. Current
T04 remains in progress: search/publication contracts, admitted authenticators,
canonical browser URLs and runtime wiring. Current
tests use the real service/repository/PolicyClient with synthetic authenticator
and PDP transport; they establish adapter behavior, not live identity admission.
Runtime must not expose this adapter before lifecycle policy and independent
event-delivery contracts are accepted and readiness can be satisfied.
## Memo commands and packet custody
`memo` uses the existing application Memo JSON representation returned by GET,
with the model's defaults available on input. `lifecycle_commands.decode_memo`
derives closed fields and types from the existing dataclasses; it does not add
fields to or replace the governed portable Decision Memo schema. This is the
current application's projection, not a claim to accept every portable schema
field. Nested unknown fields, wrong scalar types (including booleans as version
numbers), duplicate packet/highlight ids, invalid digests, excessive nesting and
secret markers are rejected. Approval pins and seals cannot be submitted. The
question must pass the existing one-question check. Both L0 and L3 use the same
model/decoder. The memo's proposed binding principal is not caller identity.
Registration is `{ "memo": <Memo JSON>, "documents": [...] }`. Revision adds
`expected_version` and a nonempty `summary`; its memo id must match the path and
its version must be exactly expected_version + 1. Every mutation requires
Idempotency-Key. Register/revise receipts include a relative API resource link
`/v1/decisions/{id}?version={version}`. This does not claim that an unpublished
draft has a human review URL or that the API is publicly mounted.
Each document has exactly `digest`, `media_type`, `content_base64`. At most 100
documents are allowed; the total JSON request remains bounded at 256 KiB and
each decoded document at 192 KiB. SHA-256 must match the supplied bytes and a
packet entry. Duplicate/orphan documents are refused. UTF-8 packet content uses
the existing secret-marker scan. Supplied hashes are never proof of custody.
Registration must include all packet content. Revision may retain content from
the current revision's packet, and must supply bytes for any new digest. Knowing
a digest stored under a different record grants no reuse capability.
Memo, new documents, lifecycle event, pending delivery and idempotency receipt
commit together. A write failure rolls them all back. Policy authorization covers
the command including documents; identity and decision freshness are rechecked
in the write transaction. Replays are reauthorized and return the original event.
Conflicting content for a key, stale revision preconditions and attempts to
replace an existing digest's media type produce 409 without a partial write.