Parent: #66
type = decision
status = approved
implementation status = deferred
Problem
Current save -> publish -> dispatch flow can leave state and integration effects out of sync on partial failure.
The current Rondel consistency boundary is Rondel.materialize, which sequences:
- Save state.
- Publish domain events.
- Dispatch outbound commands to other bounded contexts.
If save succeeds and a later publish/dispatch step fails, state has already moved forward. For paid rondel movement, that can leave a pending movement in Rondel state without a durable Accounting command, or leave Accounting/Rondel reconciliation dependent on non-durable bus delivery.
Current code nuance
The terminal sandbox is intentionally in-memory and fire-and-forget:
RondelHost.Execute and AccountingHost.Execute return enqueue acknowledgment only.
- The terminal bus suppresses subscriber exceptions, so a successful
Publish call means the event was offered to current subscribers, not that every subscriber handled it successfully.
Accounting is currently stateless and auto-approves charges.
This means terminal behavior is acceptable for the current sandbox, but it is not a durable/web consistency guarantee.
Failure matrix
| Step |
Failure mode |
Current behavior |
Risk |
| Save fails |
Store returns Error |
Handler fails before publish/dispatch |
Clean failure; no later effects should occur |
| Save succeeds, publish delivery/handling fails |
Bus subscriber throws or delivery is not durable |
Terminal bus suppresses subscriber exceptions |
State/event divergence; publish success does not prove handling |
| Save succeeds, dispatch fails |
Dispatch returns Error or target is unavailable |
Handler fails after state save |
State saved but outbound command missing |
| Dispatch enqueue succeeds, Accounting processing fails |
Accounting mailbox later fails |
Caller already received enqueue acknowledgment; notification may be published |
Rondel has pending movement, Accounting may not process charge |
| Accounting publishes return event, Rondel handling fails |
Return event delivery/handling is non-durable |
Event can be missed or swallowed in terminal bus path |
Accounting has outcome, Rondel remains pending |
| Relay redelivers a message in future durable flow |
Relay crashes after delivery before acknowledgment |
Duplicate delivery is expected under at-least-once delivery |
Consumers must be idempotent |
Decision
Adopt transactional outbox plus idempotent inbox for durable/web deployments.
The durable commit must atomically persist domain state changes and outbound messages. A relay then delivers pending messages with at-least-once semantics. Consumers must be idempotent through an inbox/processed-message mechanism or equivalent business-level idempotency.
This is the selected strategy because:
- outbox prevents
state committed, message lost;
- inbox/idempotent consumers handle duplicate delivery, which outbox relay cannot avoid;
- the model aligns with future HTTP
202 Accepted/eventual consistency semantics;
- it keeps the terminal sandbox simple while preserving a clear path to durable behavior.
Non-decision for current terminal sandbox
Do not implement an in-memory outbox for the current terminal sandbox.
An in-memory outbox would not survive process failure and would not provide the durability guarantee this decision is about. The current supervised mailbox and in-memory store remain acceptable for local sandbox work.
Implementation direction
When implementation starts, prefer an explicit effect commit boundary over transparent middleware capture.
Target shape:
- introduce
RondelEffects as data for state, domain events, and outbound commands;
- introduce
CommitRondelEffects as the boundary where infrastructure chooses direct commit or durable outbox commit;
- preserve current terminal direct-commit behavior while making the consistency boundary explicit and testable.
Transparent middleware capture may be used as a tactical bridge if needed, but it is not the target architecture. It gives Save, Publish, and Dispatch two meanings depending on context, which is too implicit for the permanent consistency boundary.
Follow-up work
Acceptance criteria
Consequences
Durable/web readiness requires more than replacing the bus with retries. The architecture needs a named commit boundary, durable outbox storage and relay, and idempotent consumers. Implementation remains deferred until the durable persistence/web phase, but future work should align with this decision.
Parent: #66
type = decision
status = approved
implementation status = deferred
Problem
Current
save -> publish -> dispatchflow can leave state and integration effects out of sync on partial failure.The current Rondel consistency boundary is
Rondel.materialize, which sequences:If save succeeds and a later publish/dispatch step fails, state has already moved forward. For paid rondel movement, that can leave a pending movement in Rondel state without a durable Accounting command, or leave Accounting/Rondel reconciliation dependent on non-durable bus delivery.
Current code nuance
The terminal sandbox is intentionally in-memory and fire-and-forget:
RondelHost.ExecuteandAccountingHost.Executereturn enqueue acknowledgment only.Publishcall means the event was offered to current subscribers, not that every subscriber handled it successfully.Accountingis currently stateless and auto-approves charges.This means terminal behavior is acceptable for the current sandbox, but it is not a durable/web consistency guarantee.
Failure matrix
ErrorErroror target is unavailableDecision
Adopt transactional outbox plus idempotent inbox for durable/web deployments.
The durable commit must atomically persist domain state changes and outbound messages. A relay then delivers pending messages with at-least-once semantics. Consumers must be idempotent through an inbox/processed-message mechanism or equivalent business-level idempotency.
This is the selected strategy because:
state committed, message lost;202 Accepted/eventual consistency semantics;Non-decision for current terminal sandbox
Do not implement an in-memory outbox for the current terminal sandbox.
An in-memory outbox would not survive process failure and would not provide the durability guarantee this decision is about. The current supervised mailbox and in-memory store remain acceptable for local sandbox work.
Implementation direction
When implementation starts, prefer an explicit effect commit boundary over transparent middleware capture.
Target shape:
RondelEffectsas data for state, domain events, and outbound commands;CommitRondelEffectsas the boundary where infrastructure chooses direct commit or durable outbox commit;Transparent middleware capture may be used as a tactical bridge if needed, but it is not the target architecture. It gives
Save,Publish, andDispatchtwo meanings depending on context, which is too implicit for the permanent consistency boundary.Follow-up work
Acceptance criteria
Consequences
Durable/web readiness requires more than replacing the bus with retries. The architecture needs a named commit boundary, durable outbox storage and relay, and idempotent consumers. Implementation remains deferred until the durable persistence/web phase, but future work should align with this decision.