How-to: Block Execution (Guard / Policy Middleware)
Problem
You need to forbid certain operations from running through Postboy. Typical examples:
“Disable all Execute operations in read-only mode”
“Block specific executors/commands unless the user is authenticated”
“Prevent destructive operations in production”
“Reject callback requests when a feature flag is off”
You want this to happen centrally (without modifying every handler), with clear diagnostics when the operation is blocked.
Prerequisites
Middleware is attached to the Postboy bus.
You understand the basics of middleware:
canHandle(context)decides whether the middleware appliesbefore(context)can return Interrupt to cancel the operation
You know which stage you intend to block:
Execute (most common for hard guards)
Callback (request-like flows)
Publish (rare; usually not recommended)
Steps
1) Choose the stage(s) you want to block
Start with stage-first scoping.
Common choices:
Block Execute for dangerous synchronous operations.
Block Callback for requests that should be rejected early.
Avoid blocking Publish unless you intentionally want to suppress events.
2) Decide the policy rule and its scope
Write your policy rule in one sentence. Examples:
“In readonly mode, block any executor that mutates state.”
“Only admins can run DeleteUserExecutor.”
“Feature X is disabled → block FeatureXQuery callback.”
Then decide scope:
All messages in a stage
A subset by message type/category
A subset by naming conventions / roles
A subset by runtime environment
3) Implement a guard middleware: filter in canHandle, block in before
Design pattern:
canHandle: narrow the middleware to only the relevant stage + message subset.before: evaluate the policy and return Interrupt when it must not proceed.
Important:
Keep
canHandlecheap and side-effect free.Keep
beforedeterministic (no hidden I/O, no random decisions).
4) Ensure cancellation diagnostics are meaningful
When a guard blocks an operation, the caller will receive a cancellation error.
Make it diagnosable by ensuring:
middleware has a stable name (use an explicit name if needed)
the policy reason is clear and safe to log
do not include secrets (tokens, PII)
do include a searchable “policy id” or short reason text
5) Attach the guard middleware early in the chain
If you have multiple middlewares:
attach guard middleware early so it blocks before expensive work
decide whether observability middleware should:
log attempts (place earlier than guard), or
log only successful operations (place later than guard)
6) Verify: blocked operations do not reach handlers
Test the guard in two scenarios:
Allowed case: handler runs normally.
Blocked case: handler is not invoked; caller receives a cancellation error.
For Callback flows, also verify:
the returned observable emits an error (or otherwise signals failure, depending on your API conventions).
Verification (expected outcome)
Expected behavior when blocked
The operation is cancelled in the
beforephase.No handler/subscriber runs.
The caller receives a cancellation error that includes:
stage
message id
cancelled-by middleware name
reason text (if provided)
Expected behavior when allowed
The middleware returns Continue.
The operation proceeds normally.
afterexecutes for successful operations (subject to the general lifecycle contract).
Diagram: guard middleware decision point
@startuml title Guard middleware blocks execution via Interrupt
actor Caller participant "Postboy" as Bus participant "Guard middleware" as Guard participant "Handlers/Subscribers" as H
Caller -> Bus: start operation (Execute / Callback / Publish) Bus -> Guard: canHandle(context)? alt not relevant Bus -> H: run operation Bus --> Caller: completed else relevant Bus -> Guard: before(context) alt policy violated Guard --> Bus: Interrupt Bus --> Caller: cancelled (error) else policy ok Guard --> Bus: Continue Bus -> H: run operation Bus --> Caller: completed (result/stream) end end
@enduml
Troubleshooting
“The operation is blocked, but I can’t tell why”
Likely causes:
guard middleware uses a generic name
reason text is missing or too vague
multiple guard middlewares exist and you don’t know which one cancelled
Fix steps:
Give each guard middleware a unique, stable name (e.g.,
ReadonlyGuard).Use a short, searchable reason format:
POLICY_READONLY_EXECUTE_BLOCKED: readonly modePOLICY_FEATURE_DISABLED: featureX
Temporarily log every guard decision with stage + message id + decision.
“Blocking doesn’t work for some operations”
Likely causes:
wrong stage: you filtered for Execute but the operation is Callback (or vice versa)
canHandlefilters out messages you intended to block
Fix steps:
Print stage in debug logs while validating.
Narrow stage filtering first, then add message filtering.
“I blocked Publish and now my system is inconsistent”
Cause:
suppressing events can hide important signals and break eventual consistency assumptions.
Fix:
Prefer guarding the command/callback/executor that triggers the event, not the event itself.
Best practices
Prefer blocking at Execute and Callback stages (clear intention).
Keep policies transparent: predictable behavior + good diagnostics.
Avoid I/O inside
before(guards should be fast).Use
canHandleto reduce the blast radius of guard logic.Document middleware ordering when multiple policies exist.
Next steps
[Interrupt/cancel semantics: what happens when middleware blocks an operation][How-to: filter middleware to specific messages][Middleware stages: Publish / Callback / Execute][How-to: logging/tracing middleware (including cancelled attempts)][Troubleshooting: diagnosing unexpected cancels]