Decision & Cancellation Reference
Summary
This page defines the exact meaning of middleware decisions (Continue / Interrupt) and what “cancellation” means in Postboy middleware terms: when it happens, what gets executed (and what does not), and what diagnostic information you can expect.
This is a reference page. For motivation and patterns, see:
Middleware: Interrupt / Cancel (concept)How-to: Block execution (guard middleware)
Applies to
Middleware functionality available since Postboy v3.5.
MiddlewareDecisionType
Enum values
MiddlewareDecisionType.ContinueMiddlewareDecisionType.Interrupt
Meaning
Value | Meaning | Effect on operation |
|---|---|---|
Continue | Allow processing to proceed | Next middleware runs, then the stage operation executes |
Interrupt | Stop processing immediately | Operation is cancelled before handlers/subscribers run |
MiddlewareDecision (return type of before)
Middleware before(context) returns a MiddlewareDecision object.
Required fields
Field | Type | Required | Notes |
|---|---|---|---|
type | MiddlewareDecisionType | yes | Continue or Interrupt |
Recommended fields (if supported by your project conventions)
Even if the library’s minimal contract is just type, in practice you often want a stable policy identifier and a human-readable reason. If your project exposes those via the cancellation error, keep them:
short
searchable
safe to log
What counts as “cancellation”
An operation is considered “cancelled by middleware” when:
a middleware runs
before(context), andit returns a decision with
type = Interrupt.
Cancellation is a control-flow stop at the bus boundary.
Non-cancellation cases (important)
These are not “middleware cancellation”:
a handler throws an error during execution
a callback result completes with an error later
a subscriber unsubscribes
an operation is not executed because nothing is registered (if applicable in your setup)
Those are failures or runtime conditions, but not middleware-driven cancellation.
Cancellation semantics (exact effects)
1) Cancellation happens before the stage operation
If any middleware interrupts in before:
the stage operation does not run
Publish: message is not broadcast
Callback: request is not delivered
Execute: handler/function is not invoked
2) Cancellation is immediate and chain-short-circuited
The first middleware that interrupts stops the chain.
No later middleware is evaluated for that operation.
3) after() is not executed for that operation
Because the stage operation never runs, the post phase is not reached:
after(context, result?)is not called for any middleware for that operation.
4) A cancellation error is raised
Postboy raises a cancellation error containing diagnostic metadata:
stage
message id (if available)
middleware name (the one that cancelled)
optional reason text
Use this error for:
debugging
audit logging
mapping to a user-facing “forbidden/blocked” response (in your app layer)
Ordering rules (deterministic precedence)
Middleware order is deterministic:
evaluated in registration order
Precedence rule:
the earliest middleware in the chain that returns Interrupt “wins”
its name/reason becomes the cancellation identity
This matters when you have multiple policies. If precedence is important, document the ordering.
Stage-specific notes (cancellation impact)
Publish stage
Interrupt prevents broadcasting.
Recommended usage: rare; prefer guarding the cause (commands/executors) instead of blocking events.
Callback stage
Interrupt prevents delivering the request to subscribers.
Caller receives cancellation through the callback call boundary (often as an error in the returned stream).
Execute stage
Interrupt prevents running the executor handler/function.
Caller receives a cancellation error immediately (synchronous boundary).
Reference matrix: which hooks run on cancellation?
Phase | Hook | Runs if canHandle is false? | Runs if decision is Interrupt? |
|---|---|---|---|
Filter | canHandle | yes (it is the filter) | not applicable |
Pre | before | no | yes (this is where Interrupt occurs) |
Operation | handler/subscribers | not applicable | no |
Post | after | no | no |
Cleanup | dispose | not related to a single operation | not related to a single operation |
Key takeaway:
Interrupt affects only the current operation.
Dispose is lifecycle-level, not operation-level.
Diagnostics reference (what to log/search)
When handling or troubleshooting cancellations, record:
stage
message id
message type/category (stable)
cancelled-by middleware name
reason (if present)
Do not put in logs/metrics:
secrets
PII
high-cardinality identifiers as metric labels (message id is fine for logs, not for metrics)
Diagram: cancellation short-circuits the chain
@startuml title Cancellation by middleware Interrupt (short-circuit)
participant "Postboy" as Bus participant "Middleware A" as A participant "Middleware B" as B participant "Handler/Subscribers" as H
Bus -> A: canHandle? (true) Bus -> A: before() -> Interrupt note over A: decides to cancel Bus --> Bus: raise cancellation error note over B: B is not evaluated note over H: operation never runs @enduml
Troubleshooting quick lookup
“Why do I not see after() logs?”
Most common cause:
the operation was cancelled by an earlier middleware (Interrupt), so after() never ran.
Next step:
log attempt in before()
ensure logging middleware is placed before guards if you want to see cancelled attempts
“Which middleware cancelled?”
Use cancellation error metadata:
middleware name
stage
message id
If metadata is missing or ambiguous:
set explicit middleware names
keep one-policy-per-middleware to improve diagnostics
Next steps
[Middleware API reference][Interrupt / Cancel (concept page)][How-to: block execution (guard middleware)][How-to: logging/tracing including cancelled attempts]