hub-core/docs/operation-outcome-audit.md
tegwick f0eff0ac92
Some checks failed
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / pytest-smoke (push) Failing after 4s
feat: atomically journal and deliver authorized native operation outcomes
Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
2026-09-28 14:00:26 +02:00

5.2 KiB

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.