2026-09-28 14:59:33 +02:00
|
|
|
# Transaction-linked operation outcomes
|
2026-09-28 14:00:26 +02:00
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2026-09-28 14:59:33 +02:00
|
|
|
Implemented SQL compatibility writes share this transaction boundary: hub,
|
|
|
|
|
manifest create/update/activate, consumer, API key, widget and interaction-event
|
|
|
|
|
writes, through both `/api/v2` and root aliases. Key issuance also updates the
|
|
|
|
|
consumer in the same transaction; an outbox failure rolls both changes back.
|
|
|
|
|
Generated keys never enter the ledger/outcome envelope and cannot authenticate
|
|
|
|
|
enforced routes, which still require verified OIDC identity.
|
|
|
|
|
|
|
|
|
|
Unimplemented token issuance and deferred POST operations return `501`, including
|
|
|
|
|
in OpenAPI, rather than acknowledging a mutation that did not occur. Missing
|
|
|
|
|
manifest update/activation returns `404`. Neither creates a committed outcome.
|
|
|
|
|
Interaction responses return the persisted ID even when the caller supplies an ID.
|
|
|
|
|
|
|
|
|
|
This does not claim outcome coverage for arbitrary embedded-host mutations,
|
|
|
|
|
background projection refresh,
|
2026-09-28 14:00:26 +02:00
|
|
|
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,
|
2026-09-28 14:59:33 +02:00
|
|
|
all native and implemented SQL compatibility mutation families, actor/decision attribution, concurrent request
|
2026-09-28 14:00:26 +02:00
|
|
|
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.
|
|
|
|
|
|
2026-09-28 14:22:05 +02:00
|
|
|
Disposable PostgreSQL tests now verify multiworker lock scheduling, the full
|
2026-09-28 14:59:33 +02:00
|
|
|
migration chain, native and compatibility-key rollback, persisted retries and
|
|
|
|
|
process-exit replay. See the
|
2026-09-28 14:22:05 +02:00
|
|
|
[PostgreSQL gate](conformance.md#disposable-postgresql-gate). Production grants,
|
|
|
|
|
retention and deployed failure-detection/receiver acceptance still require receipts.
|
2026-09-28 14:00:26 +02:00
|
|
|
|
|
|
|
|
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.
|