Skip to main content
[AVAILABLE]. packages/policy, 72 tests. CLAIMS.md 2.2/2.3.

What it is

A Policy is a named, versioned, ordered list of rules. Each rule pairs a condition (a boolean expression over named “signals”) with an outcome: approve, reject, or require override. PolicyEngine evaluates a transaction’s signals against a policy’s rules and returns exactly one Decision.

Why it exists

Every action Parmana authorizes has to be justified by something a human can read and a machine can evaluate identically every time. A policy is that something: not a prompt, not a model call, a plain data structure that produces the same outcome for the same input, every time, forever. That’s what makes an authorized action defensible after the fact.

How it behaves

The runtime executes exactly one, explicitly referenced policy, identified by PolicyReference { name, version, schemaVersion } on the Business Transaction. It does not discover policies, negotiate them, auto-select “latest,” or substitute an alternative. PolicyRouter loads by exact name and version; PolicyValidator checks the loaded policy’s identity before evaluation runs. Rules are evaluated sequentially, first match wins:
If no rule matches, findFirstMatch returns null, and the outcome defaults to REJECT (PolicyEngine.ts:191-206, toOutcome’s default case). There is no code path where an unmatched transaction is approved. This is what “fail closed” means here in practice: the absence of a rule is a denial, not a pass-through. A trailing { "always": true } rule typically makes the reject-by-default behavior explicit in the policy document itself.

Binding a signal to the executed Intent

Evaluating transaction.signals alone has a gap: nothing, by itself, connects those signals to transaction.intent — the action, target, and parameters that actually get signed and executed if the Decision is APPROVED. A caller could declare signals describing a small, fully-verified action while intent executes something else entirely, and still receive a signed APPROVED trust record for it. This was a real, live bypass, not a hypothetical one — see Security for what it looked like in practice and how severe it was. Policy.boundSignals is the fix: an optional map from a signal key to an intent dot-path. Every entry declares “this signal must equal this exact field of what’s actually executed”:
SignalIntentBinder (packages/policy/src/SignalIntentBinder.ts) checks every declared binding by strict equality — a signal the caller never declared at all counts as a violation, not a pass, since undefined almost never equals a real intent value:
RuntimeEngine.execute (packages/runtime/src/RuntimeEngine.ts) runs this check immediately before PolicyEngine.evaluate, over the exact signals about to be evaluated and the exact intent that will be signed and executed if approved. A violation is built into an ordinary PolicyDecision with outcome REJECT and a reason naming every mismatched field — no rule is ever evaluated, and no authorization is ever generated for a mismatched request, the same fail-closed shape as any other policy rejection (see Write your first policy for what a rejection actually does).
Binding is opt-in, per field, per policy. boundSignals only closes the decoupling between what a policy evaluates and what executes, for the specific fields a policy author declares bound. It does not independently verify that an unbound signal is actually true — vendorVerified, paymentApproved, riskScore, and similar remain caller-declared attestations with no independent verification, exactly as before. Closing that gap for real means fetch-verifying those signals from an independent source, the way RazorpaySettlementProcessor already does for webhook-derived settlement facts — real, valuable, and not yet extended to policy signals generally. See Razorpay for the one connector where fetch-verification of some signals is real today, and its exact scope.

Minimal example

From policies/vendor-payment/2.0.0/policy.json, a real policy in this repo:

What a Decision records

The signals evaluated are captured on the Decision itself, this is what replay reconstructs from: given the same recorded signals and the same policy version, re-evaluation must produce the same outcome. TrustChainValidationComponent and RuntimeEngine refuse to execute when required trust artifacts are missing or the Decision is not APPROVED (CLAIMS.md 2.4). Only an APPROVED Decision can produce a signed execution authorization.

Next

Execution authorization

What an APPROVED decision becomes: a signed, single-use envelope.

Determinism and clocks

What “deterministic” means precisely, and where it does and doesn’t apply.