Skip to main content
[AVAILABLE], every command below was run in this session against the repo’s actual code, commit 651497a.

Goal

Write a new Policy from scratch, distinct from the shipped vendor-payment one, and confirm it approves and rejects correctly.

Prerequisites

  • The repo cloned and npm install run.
  • Read Policies and the decision first if you haven’t, this guide assumes you know what first-match-wins and fail-closed mean.

Steps

1. Write the policy document

A Policy is a JSON file at <policyDir>/<name>/<version>/policy.json. Create policies/high-value-payment/1.0.0/policy.json:
Note the trailing reject-default rule: without it, an unmatched transaction still rejects (fail-closed is the engine’s default, not something the policy has to opt into), but writing it explicitly documents the reject-by-default behavior for anyone reading the policy file itself.
This policy declares no boundSignals, and that’s fine here — high-value-payment is a tutorial policy with no target/parameters-shaped signal to bind. If a signal in your own policy is supposed to describe a fact about intent (an amount, a target account, a vendor id), declare it in boundSignals so SignalIntentBinder checks it before evaluation runs, rather than trusting a caller’s declaration unchecked — see Policies and the decision for the mechanism and why it exists.

2. Reference it from a Business Transaction

3. Evaluate it

This exact scenario already exists as examples/tutorials/14-custom-policy/, reuse it rather than writing a parallel harness: it builds a FilePolicyRepository pointed at a local policies/ directory and executes a transaction through RuntimeFactory.create() directly, no server required.

Verify

Real output, this session:
Now confirm the reject path. In a scratch copy of transaction.json, set "financeDirectorApproved": false, and rerun. This is what actually happens, tested in this session, not what you might expect:
A REJECTED decision does not come back as a normal object with outcome: "REJECTED", RuntimeFactory’s ExecutionTrustApplication.execute() throws a RuntimeError whose message is the matched rule’s rejection reason. examples/tutorials/14-custom-policy/run.ts doesn’t catch this specially, so an uncaught rejection crashes the script, that’s expected, not a bug in the tutorial, a caller that wants to handle rejection gracefully needs a try/catch around execute(). The reject-high-value-without-director-approval rule is what matched, confirmed by the message, first-match-wins doing its job on different signals, same policy document, no code change.

Troubleshoot

  • Decision comes back REJECTED with "reason": "One or more required policy conditions were not satisfied.", not the reason you expected. Your transaction’s signals matched no earlier rule, only the trailing default. Check signalsSchema against what you actually sent, a typo’d signal name is silently undefined, which fails every eq/gt/lte condition.
  • PolicyValidator throws about policy identity. Your Business Transaction’s policy.name/policy.version doesn’t match the loaded file’s policyId/policyVersion exactly, these are checked, not inferred from the file path.
  • Wrong policy loaded, or “policy not found.” FilePolicyRepository resolves <policyDir>/<name>/<version>/policy.json literally, confirm the directory structure matches, including the version folder.

Next

Authorize and execute an action end to end

What happens after a policy approves: signing, gateway verification, execution.

Policies and the decision

The concept this guide exercises.