FabricFabricPlatform
Platform referenceGovernance

Policy enforcement

How the runtime dispatches policies, aggregates outcomes, and halts blocked invocations.

This page describes the runtime dispatch mechanism. For the contract a policy implements, see policy model.

Dispatch sequence

Code dispatch

For a code policy, the dispatcher resolves the evaluator from the registry by codeEvaluatorPolicyId (default: the policy's own ID). Three failure modes:

SituationResultEvidence carried
Evaluator not registeredblock with reason "No evaluator registered for policy …"code.registered: false, requestedPolicyId
Evaluator throwsSurfaces as failed invocation statusStack trace recorded; outcome not persisted
Evaluator returns blockblockcode.policyId, code.version, dispatchPath: ["code"]

A missing evaluator is treated as block, not pass. The default is deny.

Data dispatch

For a data policy, the dispatcher:

  1. Validates the definition (depth, count, path segment counts, operator compatibility — see invariants).
  2. If invalid → block with definitionStatus: "invalid" and the validation errors.
  3. If missing → block with definitionStatus: "missing".
  4. Otherwise, evaluates each top-level condition. Each condition tree's result rolls up via all / any / not / parameter / comparison.

Aggregation within a single data policy:

top-level results
  → first "block"        wins
  → otherwise first "warn"
  → otherwise definition.defaultResult ?? "pass"

Hybrid dispatch

interface PolicyFallbackDefinition {
  codeEvaluatorPolicyId: PolicyId;
  onResults?: ("pass" | "warn" | "block")[];   // default ["warn", "block"]
  triggers?: PolicyFallbackTrigger[];          // default all three
}

The dispatcher evaluates data first. If definitionStatus is missing or invalid (and the corresponding trigger is enabled), the fallback fires. Otherwise the data outcome's result is checked against onResults. If matched and data_result is in triggers, the fallback fires.

The fallback runs the code evaluator with the original context. The final outcome carries combined dispatchEvidence:

  • data block — what the data check evaluated.
  • fallback block — that the fallback fired, the trigger, and the data result that caused it.
  • code block — which evaluator ran and its version.
  • dispatchPath: ["data", "fallback", "code"].

What gets persisted

Each policy outcome becomes a PolicyEvaluation row tied to the ActionInvocation. The row carries:

  • policyId, policyVersion, policyKind
  • result and optional reason
  • The full dispatchEvidence JSON
  • metadata (e.g. failedConditionId for data policies)

Halt behavior

When aggregation produces block:

  1. The invocation status moves to blocked_by_policy.
  2. The action's handler does not run.
  3. State-machine validation does not run.
  4. Adapter steps do not run.
  5. No domain events are emitted.
  6. A ComplianceBlocked platform event is emitted (subject = ActionInvocation, payload references the failing policy).

The audit trail for a blocked attempt is therefore: ActionInvocation row + one or more PolicyEvaluation rows + a ComplianceBlocked event.

See also

On this page