informed-decision/docs/specs/LifecycleServiceContract.md
tegwick b2a4b863a8 Add protected headless lifecycle application service
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f
2026-10-04 18:25:21 +02:00

90 lines
5 KiB
Markdown

# Protected lifecycle application service
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.
## Caller and authority
The identity adapter supplies an immutable `Caller`: tenant, actor subject/kind,
finite expiry and JSON identity attributes. This is trusted in-process context,
not a client-controlled payload or credential. `Caller.from_human_session`
imports the existing verified KeyCape session, preserving tenant provenance,
authentication-derived human identity, assurance, roles and groups. It carries
neither the browser token nor CSRF material into policy requests or stored events.
Agent adapters must authenticate the workload before constructing Caller. This
increment does not add an issuer, infer a tenant from a request body, treat a
claimed agent name as authentication, or authorize a new workload audience.
The existing intake TokenReview audience is not silently reused for this service.
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;
review and human disposition contracts remain unchanged.
The request carries the authenticated subject, tenant, resource `memo:<id>`,
system `informed-decision`, type `decision-memo`, exact configured policy pin,
and context:
* `contract`: `informed-decision.lifecycle.v1`;
* `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.
The command digest correlates the requested operation; it is not `view_hash`,
an approval binding digest or an authorization verdict. No presentation evidence
is supplied as an input to the decision it presents for. Advisory content is not
sent to the PDP in clear text. Identity attributes are supplied by trusted
adapters, not copied from memo content.
## Freshness, replay and evidence
Every operation, including an idempotent retry, obtains a new policy observation
and persists it using the existing Store. A deny, expired or mismatched response,
unsupported obligation or unavailable policy service prevents the operation.
The existing PolicyClient validates resource/subject/context, request digests,
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
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.
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
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.
## 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
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.
Before enabling a listener, T02/T04 must provide: reviewed lifecycle PDP package
and action/context fixtures; admitted human/workload identity adapters; closed
HTTP schemas with canonical URLs and request limits; service admission and
publication audience contracts; lifecycle audit receiver admission and readiness
accounting. Test fixtures exercise the real PolicyClient against synthetic policy
transport. They are not evidence of a deployed lifecycle policy grant.