Postboy Help

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 applies

    • before(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 canHandle cheap and side-effect free.

  • Keep before deterministic (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 before phase.

  • 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.

  • after executes 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 mode

    • POLICY_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)

  • canHandle filters 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 canHandle to 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]

29 июня 2026