FabricFabricPlatform
Platform referenceGovernance

Policy model

The PolicyEvaluator contract, three policy kinds, and dispatch evidence.

A policy in Fabric is a predicate that returns pass, warn, or block for a given action invocation. Source: packages/platform/policies/index.ts.

The evaluator contract

interface PolicyEvaluator<TDb = unknown> {
  policyId: PolicyId;          // "lending.credit_pull_consent.v1"
  version: number;
  kind?: "code";
  previewSafe?: boolean;
  evaluate: (ctx: PolicyContext<TDb>) => Promise<PolicyOutcome>;
}
interface PolicyContext<TDb> {
  tenantId: string;
  spaceId: string;
  actionInvocationId: string;
  actionId: string;
  parameters: unknown;
  db: TDb;
  now?: Date;
  mode?: "preview" | "execute";
}

The evaluator gets read-only access to db (for cross-entity checks like "has consent been recorded?") and the action's full parameters. It returns:

interface PolicyOutcome {
  policyId: PolicyId;
  policyVersion: number;
  policyKind?: PolicyKind;
  result: "pass" | "warn" | "block";
  reason?: string;
  metadata?: Record<string, unknown>;
  dispatchEvidence?: PolicyDispatchEvidence;
}

Three kinds

KindWhere the logic livesWhen to use
codeA registered TypeScript PolicyEvaluator.Anything requiring database reads or non-trivial logic.
dataA declarative DataPolicyDefinition (condition tree).Simple parameter/context comparisons that compliance teams want to inspect/edit.
hybridData first; on a configured trigger, fall back to code.Default-deny rules where the data check covers 90 % of cases and code handles edges.

Data policies

interface DataPolicyDefinition {
  conditions: DataPolicyCondition[];
  defaultResult?: PolicyEvaluationResult;
  reason?: string;
}

Conditions are one of: always, parameter, comparison, all, any, not. Comparisons are limited to exists, equals, notEquals, gt, gte, lt, lte. Path access is restricted to parameters.* and a small allowlisted set of context paths (tenantId, spaceId, actionInvocationId, actionId, mode).

The bounded shape (max depth 5, max 100 conditions, max 12 path segments — see invariants) is intentional. A data policy that needs more is a code policy.

Hybrid fallback

interface PolicyFallbackDefinition {
  codeEvaluatorPolicyId: PolicyId;
  onResults?: PolicyEvaluationResult[];   // default: ["warn", "block"]
  triggers?: PolicyFallbackTrigger[];     // default: all three triggers
  reason?: string;
}

Triggers:

  • data_result — the data outcome was in onResults.
  • missing_data_definition — the policy is hybrid but has no dataDefinition.
  • invalid_data_definition — the data definition failed validation.

Hybrid lets compliance own the common path declaratively while engineering owns the edge.

Dispatch evidence

Every outcome carries a dispatchEvidence blob recording:

interface PolicyDispatchEvidence {
  policyKind: PolicyKind;
  policyId?: PolicyId;
  policyVersion?: number;
  dispatchPath: Array<"data" | "code" | "fallback">;
  data?: { definitionVersion, definitionStatus, conditionResults, validationErrors };
  code?: { requestedPolicyId, policyId, version, registered };
  fallback?: { used, trigger, reason, fromResult, definitionVersion, codeEvaluatorPolicyId };
}

This is what makes the policy decision inspectable. A regulator does not have to trust the result; they can read why it was reached.

Aggregation

Multiple policies may attach to one action. The runtime evaluates each, then aggregates:

function aggregatePolicyOutcomes(outcomes: PolicyOutcome[]): PolicyOutcome | undefined {
  return outcomes.find((o) => o.result === "block")
      ?? outcomes.find((o) => o.result === "warn");
}

Any block halts. Otherwise the first warn is surfaced. Otherwise pass.

See also

On this page