Skip to main content

Publish a policy

A policy decides about money, so getting one live is deliberately a sequence of proofs: it parses, it is internally consistent, it was measured against your own history, a second person agreed, and the publisher proved who they were.

Click any step to read what happens there and what can refuse it.

The rules language​

A definition is JSON:

{
"policy_type": "ROUTING",
"label": "review disputed orders",
"rules": [
{
"id": "open-dispute-on-order-review",
"description": "An order with an open dispute is reviewed before more money is taken for it.",
"when": { "feature": "dispute.openCountOnOrder", "op": "gte", "value": 1 },
"then": { "decision": "REVIEW", "reasons": ["OPEN_DISPUTE_ON_ORDER"], "actions": [] },
"weight": 300
}
],
"defaults": { "decision": "ALLOW", "reasons": ["POLICY_DEFAULT"] }
}
  • Rules evaluate in order. The first whose when matches supplies the decision, reasons and actions. No match → defaults.
  • when is a predicate {feature, op, value} or a combinator: {all: […]}, {any: […]}, {not: …}.
  • Operators: eq, neq, gt, gte, lt, lte (numbers only), in, notIn, exists. A missing feature makes every predicate false except exists: false.
  • Decisions: ALLOW, STEP_UP, HOLD, REROUTE, REVIEW, DENY.
  • Reasons must come from the registry. Actions (TRUST only) include REQUIRE_SECOND_APPROVER, HOLD_PAYOUT, REQUIRE_ADMIN_APPROVAL, REQUIRE_STEP_UP, NOTIFY_OLD_CHANNEL, NOTIFY_NEW_CHANNEL, DELAY_PAYOUT_24H, DELAY_PAYOUT_72H.
  • Weight (0 to 1000) contributes to a score summed over every matched rule. The score never changes the decision; it is a severity accumulator shown on the record.

Features you can reference​

FeatureTypeApplies toMeaning
intent.amountMinor, order.amountMinornumberintentsAmount in minor units
order.category, order.channelstringintentsWhat and where; null if you did not supply them
intent.methodFamilystringintentsCARD or PAYNOW
intent.attemptCountnumberintentsAttempts so far
dispute.openCountOnOrdernumberintentsUnresolved disputes on the order
payout.amountMinor, payout.currencynumber, stringpayoutsThe instruction
payout.amountVsBaselineRationumberpayoutsAmount ÷ the seller's average successful payout over 90 days; null with no baseline
seller.verificationLevel, seller.status, seller.ageDays, seller.openCaseCountstring, string, number, numberallThe seller
beneficiary.isNew, beneficiary.ageDays, beneficiary.changeCount30dboolean, number, numberchanges, payoutsNever paid successfully; days since first seen; change requests in 30 days
beneficiary.nameMatchLevelstringchanges, payoutsEXACT, PARTIAL (provider-verified only), LOCAL_NORMALIZED, NONE, UNCHECKED
beneficiaryChange.coolingOffSatisfied, beneficiaryChange.hoursSinceRequestedboolean, numberchanges, payoutsThe pending change's clock
fulfillment.bestEvidenceTier, fulfillment.hasReversalnumber, booleanintents, payoutsStrongest qualifying evidence tier in scope (2 = signed platform event, 3 = buyer confirmation); whether any was reversed
session.recentCredentialChangebooleanchangesThe requester changed credentials in the last 24 hours
case.openCountOnSubjectnumberallOpen cases on this subject
connection.provider, connection.healthStatestringintents, payoutsFor analysis of the executed route, not for choosing it

Per-candidate routing eligibility (capability, currency, amount range, circuit state) is the routing engine's job, not a policy's. A ROUTING policy decides whether and how strictly to route, not which connection wins.

What the templates do​

trust_baseline_protective is the recommended first TRUST policy. Its rules, in order:

  1. A payout or change while a beneficiary change is still cooling off → HOLD (COOLING_OFF_ACTIVE).
  2. A payout at 3× the seller's baseline or above SGD 10,000 without at least tier-2 fulfillment evidence → HOLD with REQUIRE_SECOND_APPROVER (PAYOUT_AMOUNT_ABOVE_THRESHOLD, FULFILLMENT_EVIDENCE_TIER_TOO_LOW).
  3. First payout to a new beneficiary → STEP_UP (NEW_BENEFICIARY).
  4. A request from an account that changed credentials recently → STEP_UP (ATO_SESSION_RULE).

trust_permissive_shadow fires the same signals but always ALLOWs, so you can watch what would have held on live test traffic before you publish the protective one. Publish the shadow first, run traffic, read the reason codes on your payouts, then publish the protective policy.

Rolling back​

On a retired version's page: Request rollback opens an approval bound to both ends of the move (from the current head, to this version). A second person approves. Roll back executes with a fresh second factor. If anything publishes in between, the approval is stale and refused. Definitions never change; only the head pointer moves.