FabricFabricPlatform
Platform referenceArchitecture

Policy enforcement checkpoints

Where policies fire in the pipeline, and why the placement matters.

A policy is a predicate that gates an action. Policies are evaluated at exactly one point in the pipeline: after the ActionInvocation row is persisted, before the handler runs. This page is about why — not how (see policy model for the contract).

The placement

Policies fire after persistence but before any handler logic. This placement is deliberate.

Why after persistence?

Because an attempted action is itself an audit fact. If the policy decision happened first and a block discarded the request, you would lose evidence that the actor tried. Compliance frequently cares about attempts, not just successes — repeated denied attempts to pull credit, for example, are a signal worth recording.

The ActionInvocation row exists with status: blocked_by_policy and a reference to the failing policy. The attempt is durable.

Why before the handler?

Because the handler is the only place domain mutations happen. By the time the handler is reached, every gate has already cleared:

  • Module entitlement ✓
  • Caller permission ✓
  • Policy evaluation ✓
  • State-machine transition ✓ (for atomic actions)

Inside the handler, business logic does not need to re-check authorization. This dramatically reduces the surface for "I forgot to call assertCanX here" bugs.

Policy outcomes

Three results, defined in packages/platform/policies/index.ts:

ResultEffect
passContinue to the next stage.
warnContinue, but record the warning in dispatch evidence. Surfaced in the UI / audit trail.
blockHalt invocation. Status set to blocked_by_policy. Reason and dispatch evidence are persisted.

aggregatePolicyOutcomes runs across all evaluated policies: any block halts; otherwise the first warn is surfaced; otherwise pass.

Dispatch evidence

Every policy outcome carries a PolicyDispatchEvidence object recording:

  • Whether the policy was code, data, or hybrid.
  • The dispatch path (["data"], ["code"], or ["data", "fallback", "code"]).
  • For data policies — which conditions evaluated and how.
  • For code policies — which evaluator was registered and resolved.
  • For hybrid policies — whether and why the fallback was triggered.

This means a regulator asking "why did you allow this action?" gets a structured, machine-readable answer, not a hand-wave.

What policies do not do

Policies are gates, not orchestrators:

  • Policies don't write domain state. They read inputs and return a result.
  • Policies don't enqueue follow-up work. That's the saga's job.
  • Policies don't replace input validation. The handler's schema is the source of truth for shape; policies are the source of truth for should this be allowed.

See also

On this page