Runtime Contract
This guide defines the runtime guarantees that Excalibur provides during message execution.
Execution Modes
Dispatch runs in one of two modes:
- Local mode: message is handled in-process.
- Transport mode: message is routed through configured transport adapters.
Routing is resolved before pipeline execution.
Pipeline Contract
Execution order is:
- Dispatcher receives message + caller cancellation token.
- Route decision is resolved.
- Middleware executes in registration order.
- Final handler/transport execution occurs.
- Result is returned to caller.
Cancellation Contract
- Cancellation tokens are part of the API contract and must propagate end-to-end.
- A
DispatchAsynccall from within a handler inherits correlation and cancellation budget. - Canceled requests must not continue work in the same request pipeline.
Context Contract
When context is materialized, these fields are expected:
CorrelationIdCausationId- message identity/type metadata
Lean local paths can defer full context creation, but correlation/causation semantics must remain correct whenever context is requested.
Retry and Poison Contract
- Retry behavior is policy/profile based.
- Exhausted retries route to poison/dead-letter handling when configured.
- Dead-letter records must retain actionable failure metadata.
Payload Size Contract
-
Oversized messages are rejected at the boundary — measured before the body is deserialized — so a single large payload cannot exhaust memory, strand a batch, or poison a queue. Ingress fails closed: the message is rejected, never truncated and never silently passed.
-
Outbox publish enforces a maximum serialized payload size (
OutboxDeliveryOptions.MaxPayloadBytes, default 4 MiB); an over-limit message is rejected before staging rather than written and retried forever. -
Every transport receive/subscribe path enforces its own maximum inbound payload size before deserialization. An over-limit delivery is never handed to your handler and never deserialized, it is logged (a
…PayloadTooLargeevent), and the rest of the batch keeps processing — no poison-loop, no stranded batch:Surface Configure via Default limit Outbox publish OutboxDeliveryOptions.MaxPayloadBytes4 MiB AWS SQS IAwsSqsTransportBuilder.UseMaxPayloadBytes(int?)256 KiB (SQS message ceiling) Azure Service Bus AzureServiceBusProcessorOptions.MaxPayloadBytes256 KiB Google Pub/Sub GooglePubSubOptions.Subscriber.MaxPayloadBytes10 MiB gRPC GrpcTransportOptions.MaxPayloadBytes4 MiB Kafka KafkaConsumerTuningOptions.MaxPayloadBytes4 MiB RabbitMQ RabbitMqConsumptionOptions.MaxPayloadBytes4 MiB (nacked requeue: false) -
How the over-limit message is settled depends on whether you have given it somewhere to go, and this is the part worth configuring deliberately:
Your configuration What happens to the over-limit message A dead-letter destination is configured (for example an SQS redrive policy to a dead-letter queue, or a Pub/Sub dead-letter topic) It is preserved and routed there by the broker's own mechanism, so you keep a copy to investigate. On SQS this means the message is left to become visible again so its receive count advances — deleting it would destroy the copy the dead-letter queue exists to capture. No dead-letter destination is configured It is dropped. Retaining a message that can never be processed and has nowhere to go would redeliver it forever and stall the queue, so liveness is preferred. If you need these messages kept, configure a dead-letter destination — that is the switch. A message that is merely large is not the same as one that is corrupt: raising
MaxPayloadBytes, or setting it tonull, makes an otherwise-valid large payload processable rather than dead-lettered. -
The default is deliberately bounded (never unbounded) so the guard is never inert for a consumer who never configures one. Set
MaxPayloadBytestonullto opt out (unbounded) for larger legitimate payloads, or raise it to a specific ceiling. A non-positive value is rejected at startup (ValidateOnStart) rather than silently bricking delivery.
Release Enforcement
This contract is release-blocked by:
- transport conformance tests,
- release-blocking CI test governance gate,
- architecture/governance validation.