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

@ -77,19 +77,19 @@ legacy `save_memo` behavior. Existing stale-presentation guards see the same mem
versions. Approval-bound/sealed revision handling requires the later review
integration; this repository refuses those lifecycle revision commands.
Pending lifecycle events are durable local obligations. They are not emitted as
invented Audit Core event types or reported delivered. The existing review audit
queue remains unchanged. Before exposure, T02/T03 must settle receiver admission,
payload/privacy, delivery retries and readiness accounting for the new queue.
Runtime readiness now refuses while lifecycle events remain pending, and the
admin inspection command reports content-free lifecycle counts. No events are
marked delivered without the future admitted receiver adapter.
Pending lifecycle events are durable local obligations. The opt-in runtime audit
adapter stages immutable `informed-decision.lifecycle` commitments into the
existing transactional outbox and clears pending obligations only after a receiver
receipt. Content remains in local custody; the independent commitment contains
its digest and existence assertion. Readiness refuses while lifecycle delivery
is pending. See `LifecycleWorkspaceDelivery.md` for the platform tenant boundary,
retries, heartbeats and outstanding production admission.
## Remaining contracts
T02 remains in progress: production identity/policy composition, versioned HTTP schemas,
publication audience policy, incomplete drafts, non-cluster workload identity,
legacy record enrollment, source fetching/retention, independent evidence delivery
production publication audience policy, incomplete drafts, non-cluster workload identity,
legacy record enrollment, source fetching/retention, live independent evidence delivery
and non-approval binding integration. T03 is started with this persistence slice;
it is not complete. T04/T05 continue to own HTTP and UI exposure. Native runtime
intake and admission remain with `INFD-WP-0005`.

View file

@ -6,6 +6,8 @@ existing browser app or public ingress.
| 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 |
| `GET /v1/decisions/{id}` | Optional positive integer `version` | Existing Memo JSON for the current or specified revision |
@ -19,7 +21,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. Search, publication and disposition routes are not yet exposed.
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
@ -40,8 +42,9 @@ 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: search/publication contracts, admitted authenticators,
canonical browser URLs and runtime wiring. Current
T04 remains in progress: admitted workload authenticators and API listener
composition. The browser workspace uses verified sessions through a separate
adapter (see LifecycleWorkspaceDelivery.md). 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
@ -64,8 +67,11 @@ 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.
`/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
unmounted. 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

View file

@ -2,8 +2,8 @@
2026-10-04; `INFD-WP-0007-T02/T03`. Implemented in
`informed_decision/lifecycle_service.py`, above `LifecycleRepository`.
This is a headless Python application interface. No HTTP route or production
composition enables it yet.
This is a headless Python application interface. An optional runtime composition
enables the browser workspace; production admission is still outstanding.
## Caller and authority
@ -23,8 +23,8 @@ Non-cluster/workload composition remains T02/T04 work.
The service consumes `PolicyClient` observations. It neither evaluates policy
nor falls back to local permissions. New proposed action names are
`lifecycle.register`, `lifecycle.revise`, `lifecycle.source`,
`lifecycle.evaluation`, `lifecycle.comment`, `lifecycle.read`, and
`lifecycle.events`. These are not added to the existing review-action allowlist;
`lifecycle.evaluation`, `lifecycle.comment`, `lifecycle.read`,
`lifecycle.events`, `lifecycle.publish`, `lifecycle.search` and `lifecycle.packet`. These are not added to the existing review-action allowlist;
review and human disposition contracts remain unchanged.
The request carries the authenticated subject, tenant, resource `memo:<id>`,
@ -35,7 +35,17 @@ and context:
* `memo_version`: submitted/requested revision, or null for latest retrieval and
record-wide event retrieval;
* `command_digest`: SHA-256 of canonical command JSON, including mutation content,
expected revision and idempotency key, or the read/page parameters.
expected revision and idempotency key, or the read/page parameters;
* `record`: tenant-partitioned registration facts, original registering actor,
current revision and latest publication audience for the requested revision;
* `publication_audience`: explicit subjects/groups for a publication command.
Search uses resource `decisions`, type `decision-collection`, then independently
authorizes each candidate with `lifecycle.read`. A page examines at most 50
candidates; denied records are omitted, unavailable policy fails the request.
Opaque cursors are scoped to caller/tenant/query and expire after five minutes.
Packet access checks its own action and a revision-specific packet index; a
known content digest grants no retrieval capability.
The command digest correlates the requested operation; it is not `view_hash`,
an approval binding digest or an authorization verdict. No presentation evidence
@ -53,7 +63,8 @@ policy pins, response lifetime and the supported effect.
Mutation commands snapshot supplied content before the network call. Under the
repository's write transaction, a guard rechecks caller and decision expiry and
the persisted observation before even returning an idempotent replay. New events
the persisted observation and unchanged record/audience facts before even
returning an idempotent replay. New events
carry `body.policy_observation_id`; receipts replay their original event and
observation link while the fresh retry check remains in policy history. The
identity/policy guard does not perform network calls while holding the write lock.
@ -61,26 +72,27 @@ identity/policy guard does not perform network calls while holding the write loc
Read authorization precedes repository lookup. Denied requests therefore cannot
distinguish missing and existing records through lookup errors. The service
rechecks identity and decision expiry after retrieving content and before returning
it. Repository tenant partition enforcement also applies. Record-wide event
it, including unchanged record/audience facts. Repository tenant partition enforcement also applies. Record-wide event
access requires its own action; it does not inherit a memo-read grant.
The existing `decision_attributable: false` declaration remains in force. A
matched, recorded response is not proof of signed PDP attribution. New lifecycle
events still reside in the durable pending queue until an Audit Core contract
and delivery adapter are admitted.
events remain pending until the opt-in audit adapter obtains a receiver receipt.
See LifecycleWorkspaceDelivery.md for custody, reconciliation and admission.
## Error and integration contract
`LifecycleError` supplies bounded status/code pairs: 401 for expired identity,
403 for a policy deny, and 503 for unavailable or unusable policy evidence.
The repository retains `Conflict`, `EvidenceUnavailable` and validation errors;
the future HTTP adapter must map these to bounded envelopes without echoing
the HTTP adapter maps these to bounded envelopes without echoing
content, credentials or raw exception diagnostics.
No accept/publish endpoint is implied by this interface. Human acceptance stays
on the existing presentation/disposition path; publication needs an explicit
audience and separately authorized transition. The service supplies the shared
operations that subsequent HTTP and UI adapters will invoke.
Publication appends an immutable event for the current revision with an explicit
nonempty audience (at most 32 subjects and 32 groups, no wildcard). A successor
revision does not inherit publication. Audience facts are inputs to Access Engine,
never local permissions. Human acceptance requires the separate existing
presentation/disposition path; the lifecycle-to-review handoff is still open.
Before enabling a listener, T02/T04 must provide: reviewed lifecycle PDP package
and action/context fixtures; admitted human/workload identity adapters; closed

View file

@ -0,0 +1,70 @@
# Lifecycle workspace and delivery
2026-10-04; INFD-WP-0007-T02 through T06, source implementation.
## Human workspace
With `lifecycle_policy` configured, the signed-in home links to `/decisions`.
The workspace uses the same headless service as the API. Existing KeyCape
sessions establish identity; existing origin and CSRF checks protect forms.
Users can search, prepare a draft, record source metadata and evaluations,
comment, publish to explicit members/groups, refine, compare consecutive
revisions and download authorized packet entries. Source content is not fetched.
Packet uploads currently use the API. All forms receive fresh policy checks;
rendering a button grants no authority.
Publication records an audience for one revision. Access Engine determines access
using authenticated identity and retained record facts. Publication creates no
approval and no human disposition. New revisions require their own publication.
The workspace explicitly states that human acceptance is unavailable for these
drafts. Connecting a lifecycle revision to the existing protected presentation
and Approval Engine path remains T03/T05 work.
## Runtime and deployment inputs
The optional runtime `lifecycle_policy` object requires exactly `origin`,
`package`, `version`, `package_digest`, `caller_token_file`. Its package must
differ from the review package. No default policy or identity fallback exists.
The optional boolean `lifecycle_delivery` defaults to false. Configure both
for an admitted workspace with evidence delivery.
The deployment renderer accepts the same separate policy pins plus `pod_name`,
requires explicit `lifecycle_delivery: true` with that package, and generates
exact egress and an owner-review ingress proposal. It references the existing
mounted workload policy credential path; this does not authorize that workload
for a new package. Existing admission inputs render without lifecycle changes.
The API still requires its own admitted bearer verifier and listener composition.
## Independent evidence
On an enabled audit tick, up to 100 pending mutations are staged as immutable
`informed-decision.lifecycle` commitments in the existing outbox. The event UUID
is the delivery identity. Commitment metadata includes memo id, revision,
operation, a content digest and an existence assertion. The digest covers the
retained event and historical memo. Advisory text stays in source custody;
commitment delivery does not provide independent content reconstructability.
Pending obligations clear only after the outbox retains a receiver reference.
Lost replies retry with the same identity. The existing worker supplies bounded
delivery, reconciliation and a 24-hour lifecycle heartbeat. Undelivered evidence
keeps runtime readiness false. The current sink/reconciliation identity is
platform-scoped; other tenants fail closed at staging. Multi-tenant receiver
admission is not claimed. Kubernetes traffic readiness behavior is unchanged.
Tests use the actual local Audit Core receiver contract, including deduplication
and reconciliation. The agent-to-human collaboration scenario creates two
records, publishes/comments/refines and drains five events with unchanged runtime
configuration bytes. Its identity and PDP transport are synthetic. It proves
source behavior, not deployed workload authority or all seven acceptance cases.
## Remaining delivery work
T02/T04 require owner-admitted lifecycle policy actions/context, workload identity
and API ingress. The policy must cover all actions in LifecycleServiceContract,
including collection search and per-record checks. Existing compact-sitting pins
do not supply these grants. WP-0005 retains private intake composition work.
T03/T05 require the durable review handoff and human-only acceptance integration.
T06 requires full acceptance evidence, sender/cadence admission, immutable image,
database backup/migration and rollback verification. T07 requires the separately
approved concrete live rollout. No production pins or resources changed here.