Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
96 lines
6.9 KiB
Markdown
96 lines
6.9 KiB
Markdown
# Lifecycle HTTP adapter — registration, revisions and collaboration
|
||
|
||
2026-10-05; INFD-WP-0007. `LifecycleAPI` is a WSGI adapter over
|
||
`LifecycleService`, mounted on the admitted private 8081 listener. It adds no
|
||
route to public ingress. All routes below, including direct retained-handoff
|
||
retrieval, are deployed in service image 7f5c6cb2.
|
||
|
||
| Method and path | Input | Result |
|
||
| --- | --- | --- |
|
||
| `GET /v1/decisions` | Optional `q` (up to 200 characters), opaque `cursor` | Authorized current revision summaries and `next_cursor` |
|
||
| `POST /v1/decisions/{id}/publications` | `version`, `audience` with explicit `subjects` and `groups` lists | Durable publication event receipt |
|
||
| `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 |
|
||
| `POST /v1/decisions/{id}/review-requests` | `version`, `options` per LifecycleReviewHandoff | Durable request receipt and exact intake envelope |
|
||
| `GET /v1/decisions/{id}/review-requests` | Required positive `version` | Exact retained request receipt/envelope, checked as `lifecycle.events`; no submission |
|
||
| `GET /v1/decisions/{id}/outcomes` | Required positive `version` | Correlated review/disposition evidence and submission state |
|
||
| `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 |
|
||
| `POST /v1/decisions/{id}/sources` | `version` and source `body` per LifecycleFoundationContract | Durable event receipt |
|
||
| `POST /v1/decisions/{id}/evaluations` | `version` and evaluation `body` per LifecycleFoundationContract | Durable event receipt |
|
||
|
||
Ids are 1–256 ASCII letters/digits or `.`, `_`, `:`, `-`. POST requires exact
|
||
`Content-Type: application/json`, a declared byte length up to 256 KiB and a
|
||
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. Disposition routes are not exposed.
|
||
|
||
Authentication runs before routing or reading a mutation body. The composition
|
||
must explicitly supply a bearer-token verifier returning an authenticated
|
||
`Caller`. There is no default or testing fallback. Cookies are not credentials
|
||
for this adapter; the browser workspace uses the separate
|
||
verified session/CSRF adapter. Neither the intake SA
|
||
audience nor the browser's approval-engine access token is implicitly admitted.
|
||
|
||
Every handled command invokes the shared policy-consuming service. Clients cannot
|
||
supply tenant, actor or policy evidence in the body. Reads and retries receive
|
||
fresh checks. Denied existing/missing record reads return the same policy-denied
|
||
response. No new authorization verdict or approval mutation is produced here.
|
||
|
||
Responses are JSON with `Cache-Control: no-store` and `nosniff`. Errors are bounded
|
||
`{"error": "code"}` envelopes: 400 invalid request, 401 missing/failed/expired
|
||
identity, 403 policy denial, 404 unavailable record/route, 405 unsupported method,
|
||
409 conflict, 413 oversized body, 415 unsupported media type, 503 unavailable
|
||
service or unusable policy evidence. Raw exception text and credentials are never
|
||
returned. Upstream raw diagnostics are not forwarded.
|
||
|
||
The deployed runtime authenticates the selected State Hub service account using
|
||
Kubernetes TokenReview and the lifecycle audience. See LifecycleProductionRuntime.md.
|
||
Human workspace forms use verified KeyCape sessions through a separate adapter.
|
||
Native admission, negative identity tests and initial human acceptance are
|
||
recorded in ../evidence/2026-10-05-lifecycle-cutover/. New clients still require
|
||
owner-reviewed admission; this contract does not widen identity or listeners.
|
||
|
||
## 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}` and a `workspace_url` at
|
||
`https://decisions.coulomb.social/decisions/{id}?version={version}`. The latter
|
||
is an authorized authoring workspace, not a binding review link. The API remains
|
||
private. Search cursors are caller/tenant/query scoped, expire after five
|
||
minutes and are invalidated on process restart. Every page rechecks policy.
|
||
|
||
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.
|
||
|
||
Review handoff, its supported profile and source-revision guards are specified in
|
||
`LifecycleReviewHandoff.md`. Human acceptance remains on the protected review route.
|