informed-decision/docs/specs/LifecycleHTTPContract.md
tegwick 96dcbfdfa2 Record native authoring and discovery release; complete initial lifecycle delivery
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
2026-10-05 01:14:08 +02:00

6.9 KiB
Raw Blame History

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.