feat: atomically journal and deliver authorized native operation outcomes
Some checks failed
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / pytest-smoke (push) Failing after 4s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
tegwick 2026-09-28 14:00:26 +02:00
parent 3c5cbfbafe
commit f0eff0ac92
13 changed files with 583 additions and 28 deletions

View file

@ -56,7 +56,9 @@ A host composes `create_app(access_controller=AccessController(...))` with:
only after an operational-custody probe and explicit durable acceptance. Every allow must reach this sink before handler execution;
a failed sink blocks reads as well as writes. Authorization receipts say
`authorized`, not “operation completed.” Domain commit/outcome audit remains a
separate requirement; this source seam does not claim transactional audit.
separate concern. Native durable mutations now have a
[transaction-linked outcome outbox](operation-outcome-audit.md); compatibility
and external mutations remain outside that slice.
- The root's existing immutable issuer and subject, supplied after owner resolution.
No username, email, first-login promotion or generic role establishes root.

View file

@ -0,0 +1,90 @@
# Transaction-linked native operation outcomes
HUB-WP-0012 source candidate, 2026-09-28. Live sender admission, database rollout
and owner acceptance remain open.
## Coverage and transaction boundary
The durable native registry, message, progress-event and interaction-event writes
already append to `runtime_audit_ledger` in their business transaction. Enforced
requests now bind those ledger rows to the verified issuer/subject, actor/target
tenant, principal type, policy decision/version/caller, request digest and
server-generated authorization correlation ID. This context comes from the
access boundary, never command payloads or caller-supplied correlation headers.
It is scoped to handler execution and reset in `finally`.
The same transaction inserts one immutable `hub.operation.committed` envelope
into `runtime_outcome_outbox`, keyed by its ledger ID. Failure to insert either
record rolls back the business mutation. A rolled-back transaction has no
committed outcome; the earlier authorization receipt remains an authorization
attempt. Registry duplicate acknowledgements have `registry.duplicate` operation
records and do not pretend a new registration was created.
The envelope contains business object references and a payload hash, not message
bodies, event payloads, credentials or tokens. Business correlation IDs are
retained separately from verified authorization correlation IDs. That verified
ID and decision ID join the outcome to the pre-execution signed decision already
held by Audit Core. The outcome does not duplicate the signed envelope.
This slice covers the native durable mutation ledger. It does not claim outcome
coverage for compatibility/embedded-host mutations, background projection refresh,
external services, or in-memory development stores. Non-enforced maintenance
writes retain their existing local ledger behavior without fabricating an actor
or emitting an attributed remote outcome.
## Delivery, recovery and readiness
When a durable store and outcome-capable audit sink are composed, the runtime
starts a dispatcher and cancels it before closing clients or stores. It selects
up to 25 eligible rows per batch, one database transaction per row, and waits
one second between batches. PostgreSQL uses `FOR UPDATE SKIP LOCKED` so workers
can select different rows. Each receiver call is bounded to three seconds.
`AuditCoreSink.append_outcome` reuses the operational-custody probe, rotating
sender credential and exact accepted/duplicate receipt checks. All retries use
the same event ID, occurrence timestamp and body; the ID is also the idempotency
key. Only an accepted receipt marks the row delivered. Failed attempts retain
the row with persisted exponential retry delay (one second up to sixty seconds).
Exceptions are not stored as diagnostic text. Cancellation or a crash before the
local delivery acknowledgement leaves the row eligible for replay. Delivery is
at least once; receiver deduplication resolves a lost receipt or duplicate send.
It does not change business mutation idempotency or make a client retry safe.
The authorization audit still requires synchronous remote custody before the
handler. Queuing a committed outcome does not weaken that check. An outcome
outage after authorization cannot undo an already committed operation.
Protected readiness adds `outcome_delivery`: missing delivery composition is
`unavailable`, a pending row older than sixty seconds is `stale`, and a young or
empty backlog is `ok`. Database readiness checks that the new table is accessible.
Pending rows are never silently discarded. Delivered rows remain for inspection;
retention/pruning needs a separately admitted operational policy.
## Schema and rollout
Migration `0006_outcome_outbox` adds the table, ledger foreign key and pending-row
index. Apply it through the existing owner migration lane before deploying this
candidate and admit runtime SELECT/INSERT/UPDATE privileges. Automatic downgrade
requires an online check and refuses a nonempty pending queue. Drain pending
outcomes and preserve required custody records before schema rollback.
The Audit Core sender must admit `hub.operation.committed` for source `hub-core`
and tenant `tenant:platform`, in addition to existing authorization record classes.
This document neither issues that grant nor claims production delivery.
## Local evidence
Tests use SQLite-backed durable stores to prove atomic rollback, handler rejection,
all native mutation families, actor/decision attribution, concurrent request
isolation, persisted retry delay, reopen/replay, cancellation, stale-backlog
readiness, dispatcher drain/shutdown and migration shape/downgrade refusal.
The real Audit Core receiver/storage source accepts the eight-field envelope and
returns a duplicate receipt on replay. Its operational-readiness classification
is explicitly a test fixture; this is not live custody evidence.
PostgreSQL multiworker lock scheduling, production grants, retention and deployed
failure-detection/receiver acceptance still require integration receipts.
Validation on 2026-09-28: 366 tests pass with the opt-in owner-source suite enabled;
inventory drift, package build and isolated installed-wheel checks pass. The
wheel includes the current migration, authority-context and durable-store code.

View file

@ -46,8 +46,9 @@ at three seconds, uses TLS, and never follows redirects.
Allow is blocked until the archive accepts the authorization record. A lost
receipt blocks the business operation even if the attempt reached storage. This
is a pre-execution authorization journal, not proof that an operation committed.
There is no local success buffer or silent redaction. Domain transaction/outcome
atomicity and failure detection remain separate T03/T04 acceptance gates.
Authorization cannot use a local success buffer or silent redaction. Native
transaction outcomes now use a separate [durable outbox](operation-outcome-audit.md);
its production delivery and broader mutation coverage remain T03/T04 gates.
The exact verified signed decision is retained under `data.signed_decision` as
serialized JSON so another serialization of the archive cannot reorder its Go