Skip to content

This page is the canonical reference for policy fields, JavaScript conditions, workflow objects, and policy-writing rules. For the concept, see Policies. For operations, see Manage policies. For reusable patterns, see Policy examples.

API reference

TaskAPI reference
Create, update, lock, or unlock a policyPropose an intent
Test a policy intentPerform a dry run
Query policiesList policies, Get policy details
Check the full schemaOpenAPI specification

Create policy fields

These fields are used in a v0_CreatePolicy payload and in genesis policy entries. Genesis policy entries are embedded in a domain's policies array and do not include the type field.

FieldTypeRequiredRules
idUUID stringYesUnique policy ID.
aliasstringYes1-75 characters. Use a stable operational name.
rankintegerYes0-1000. Higher rank wins among matching policies.
intentTypesstring arrayNoIf omitted, the policy can match all intent types. Use only for deliberate fallback or emergency policies.
intentOriginenumNoUserSigned or SystemSigned. If omitted, the policy matches user-signed proposals only.
scopeenumYesSelf, Descendants, or SelfAndDescendants.
scriptingEngineenumYesJavascript_v0.
conditionobjectNoIf omitted, the condition behaves as always true.
workflowarrayNoIf omitted or null, matching intents are auto-approved. An empty array rejects matching intents.
lockenumYesUnlocked or Locked.
descriptionstringNo0-250 characters.
customPropertiesobjectYesString key-value metadata. Use {} when empty.
typestringYes for intent payloadsv0_CreatePolicy. Omit inside genesis policy entries.

Update and lock fields

v0_UpdatePolicy uses a reference instead of id so the update targets a specific policy revision.

{
  "payload": {
    "reference": {
      "id": "5b440ba5-d013-11eb-8cd0-dcfb48cfb3cb",
      "revision": 3
    },
    "alias": "policy-governance",
    "rank": 750,
    "intentTypes": ["v0_CreatePolicy", "v0_UpdatePolicy"],
    "intentOrigin": "UserSigned",
    "scope": "Self",
    "scriptingEngine": "Javascript_v0",
    "condition": {
      "expression": "context.references.users[context.request.author.id].roles.includes('policy-operator')",
      "type": "Expression"
    },
    "workflow": [
      {
        "role": "policy-operator",
        "quorum": 2,
        "type": "RoleQuorum"
      }
    ],
    "description": "Controls policy lifecycle operations.",
    "customProperties": {},
    "type": "v0_UpdatePolicy"
  }
}

v0_LockPolicy and v0_UnlockPolicy use only reference and type.

{
  "payload": {
    "reference": {
      "id": "5b440ba5-d013-11eb-8cd0-dcfb48cfb3cb",
      "revision": 3
    },
    "type": "v0_LockPolicy"
  }
}

Policy selection

When an intent is submitted, policy evaluation:

  1. Finds policies in the target domain and any applicable parent or child domains.
  2. Filters by intentTypes. A policy with omitted intentTypes can match any intent type.
  3. Filters by scope and domain governing strategy.
  4. Filters by intentOrigin. A policy with omitted intentOrigin matches user-signed proposals only.
  5. Evaluates condition. A condition must return true for the policy to remain eligible.
  6. Selects the highest-ranked eligible policy.
  7. If two eligible policies have the same rank, the older policy wins.
  8. Applies the selected policy's workflow.

If no policy matches, the intent cannot execute.

Scope values

ValueApplies to
SelfIntents targeting the policy's own domain.
DescendantsIntents targeting descendant domains only.
SelfAndDescendantsIntents targeting the policy's own domain and descendant domains.

Domain governing strategy changes how parent and child policies interact:

Governing strategyPolicy effect
ConsiderDescendantsParent and child policies can both be considered. Highest-rank eligible policy wins.
CoerceDescendantsMatching parent policies override matching child policies.

Intent origin values

intentOrigin controls whether a policy matches a normal user-signed proposal or an API-only system-signed proposal.

ValueApplies to
OmittedUser-signed proposals.
UserSignedUser-signed proposals.
SystemSignedSystem-signed proposals submitted through the API.

Existing policies that omit intentOrigin do not match system-signed proposals. To allow a service caller to submit an intent without a client-side payload signature, create or update a policy with intentOrigin: "SystemSigned".

Rank guidance

RangeTypical use
900-1000Explicit emergency or breakglass controls with narrow intent types and high quorum.
500-800High-risk governance such as policy, domain, user, and root administration.
100-500Standard operational policies.
1-99Catch-all or fallback policies that should lose to specific policies.
0Last-resort behavior or intentionally lowest priority.

Condition object

A condition is either an Expression, an And, or an Or.

{
  "condition": {
    "expression": "context.references.users[context.request.author.id].roles.includes('transaction-operator')",
    "type": "Expression"
  }
}

And and Or use a binary tree. Use left and right, not arrays.

{
  "condition": {
    "left": {
      "expression": "context.request.payload.rank >= 700",
      "type": "Expression"
    },
    "right": {
      "expression": "context.references.users[context.request.author.id].roles.includes('policy-operator')",
      "type": "Expression"
    },
    "type": "And"
  }
}

You can also use JavaScript logical operators inside one expression when that is clearer.

{
  "condition": {
    "expression": "context.request.payload.rank >= 700 && context.references.users[context.request.author.id].roles.includes('policy-operator')",
    "type": "Expression"
  }
}

Condition context

Condition expressions can use the context object. System-signed policy conditions can also use context.submitter and intentOrigin.

ObjectContains
context.requestThe intent being evaluated.
context.referencesEntities explicitly referenced by the intent. You cannot query arbitrary entities from a policy condition.
context.submitterIdentity of the user or service caller that submitted the proposal.
intentOriginProposal origin, either UserSigned or SystemSigned.

Common context.request paths:

PathContains
context.request.author.idUser ID that created the intent.
context.request.author.domainIdAuthor's domain ID.
context.request.targetDomainIdDomain targeted by the intent.
context.request.payloadIntent-specific payload.
context.request.payload.typeIntent type, such as v0_CreatePolicy.
context.request.payload.customPropertiesCustom metadata supplied in the payload, when present.

For system-signed proposals, context.request.author is not present. Use context.submitter to check the service caller instead.

Submitter paths:

PathContains
context.submitter.typeUser or Service.
context.submitter.reference.idUser ID for user-signed proposals.
context.submitter.reference.domainIdUser domain ID for user-signed proposals.
context.submitter.subjectService subject for system-signed proposals.
context.submitter.custodyRolesRoles from the service caller token for system-signed proposals. If the configured roles claim is absent, this is an empty array.

System-signed condition example:

{
  "condition": {
    "expression": "context.submitter.type === 'Service' && context.submitter.custodyRoles.includes('gas-station-service')",
    "type": "Expression"
  }
}

Useful payload paths by intent type:

Intent typeUseful paths
v0_CreateUsercontext.request.payload.roles, context.request.payload.alias, context.request.payload.loginIds
v0_UpdateUsercontext.request.payload.reference.id, context.request.payload.roles, context.request.payload.loginIds
v0_CreatePolicycontext.request.payload.rank, context.request.payload.intentTypes, context.request.payload.scope, context.request.payload.workflow
v0_UpdatePolicycontext.request.payload.reference.id, context.request.payload.rank, context.request.payload.intentTypes, context.request.payload.scope, context.request.payload.workflow
v0_CreateDomaincontext.request.payload.alias, context.request.payload.governingStrategy, context.request.payload.permissions, context.request.payload.childrenDomainIds
v0_UpdateDomainPermissionscontext.request.payload.reference.id, context.request.payload.permissions.readAccess
v0_CreateTransactionOrdercontext.request.payload.accountId, context.request.payload.ledgerId, context.request.payload.parameters
v0_ReleaseQuarantinedTransfersPayload fields for the quarantine release request. Use existence checks before accessing optional fields.

Common context.references collections:

CollectionAccess patternUse when
Userscontext.references.users[userId]Check the author's current roles or target user state.
Accountscontext.references.accounts[accountId]Check ledger, account metadata, or account custom properties.
Endpointscontext.references.endpoints[endpointId]Check endpoint trust score or endpoint metadata.
Vaultscontext.references.vaults[vaultId]Check vault properties for account or vault operations.
Tickerscontext.references.tickers[tickerId]Check ticker or asset metadata when referenced.
Domainscontext.references.domains[domainId]Check domain metadata when referenced.

The following diagram shows how a condition can navigate from an intent payload to a referenced account's custom properties:

Context references

JavaScript rules for conditions

Conditions are JavaScript expressions. Write one expression that evaluates to true or false.

RuleGuidance
Use Javascript_v0This is the supported scripting engine.
Return a booleanThe expression should evaluate to true or false for every matching intent.
Prefer strict pathsCheck the actual payload shape for each intent type in the API reference before writing paths.
Check optional fieldsUse hasOwnProperty, null checks, or array length checks before accessing optional nested fields.
Use role names exactlyRole names are case-sensitive and must match user roles.
Keep expressions deterministicAvoid relying on external state. Policies cannot call APIs or query arbitrary entities.

Supported JavaScript patterns:

PatternExample
Comparisoncontext.request.payload.rank >= 700
Logical operatorsa && b, `a
Array includesroles.includes('compliance')
Array some/every/filteroutputs.some(output => BigInt(output.amount) > 100000000n)
Array reduceoutputs.reduce((sum, output) => sum + BigInt(output.amount), 0n)
String methodsalias.startsWith('prod-'), alias.includes('treasury')
Object checkspayload.hasOwnProperty('parameters')
JSON methodsJSON.parse(rawTx), JSON.stringify(roles)
Date methodsnew Date().getHours(), new Date().getDay()

Amount handling:

  • Use BigInt for amount comparisons.
  • Use an n suffix for integer literals, such as 50000000000n.
  • When the payload value is a string, wrap it with BigInt(...).
  • For multi-output payloads, sum amounts with reduce.
{
  "condition": {
    "expression": "context.request.payload.parameters.outputs.reduce((sum, output) => sum + BigInt(output.amount), 0n) >= 500000000n",
    "type": "Expression"
  }
}

Null-safe nested access:

{
  "condition": {
    "expression": "context.request.payload.hasOwnProperty('parameters') && context.request.payload.parameters != null && context.request.payload.parameters.hasOwnProperty('destination') && context.request.payload.parameters.destination.type == 'Endpoint'",
    "type": "Expression"
  }
}

Condition examples

High-risk Bitcoin transaction

This condition matches when the author is an operator, the transaction parameters are Bitcoin, and either the amount is at least 1 BTC or the source account has a custom property named escalatePolicy set to true.

{
  "expression": "context.references.users[context.request.author.id].roles.includes('operator') && context.request.payload.hasOwnProperty('parameters') && context.request.payload.parameters != null && context.request.payload.parameters.type == 'Bitcoin' && (context.request.payload.parameters.outputs.reduce((sum, output) => sum + BigInt(output.amount), 0n) >= 100000000n || context.references.accounts[context.request.payload.accountId].metadata.customProperties.escalatePolicy == 'true')",
  "type": "Expression"
}

Medium-risk Bitcoin transaction

This condition matches operator-created Bitcoin transactions below 1 BTC where the account is not escalated and the destination is either an unknown address or an endpoint with a trust score of 50 or lower.

{
  "left": {
    "expression": "context.references.users[context.request.author.id].roles.includes('operator') && context.request.payload.hasOwnProperty('parameters') && context.request.payload.parameters != null && context.request.payload.parameters.type == 'Bitcoin' && context.request.payload.parameters.outputs.reduce((sum, output) => sum + BigInt(output.amount), 0n) <= 100000000n && context.references.accounts[context.request.payload.accountId].metadata.customProperties.escalatePolicy != 'true' && context.request.payload.parameters.outputs[0].destination.type == 'Address'",
    "type": "Expression"
  },
  "right": {
    "expression": "context.references.users[context.request.author.id].roles.includes('operator') && context.request.payload.hasOwnProperty('parameters') && context.request.payload.parameters != null && context.request.payload.parameters.type == 'Bitcoin' && context.request.payload.parameters.outputs.reduce((sum, output) => sum + BigInt(output.amount), 0n) <= 100000000n && context.references.accounts[context.request.payload.accountId].metadata.customProperties.escalatePolicy != 'true' && context.request.payload.parameters.outputs[0].destination.type == 'Endpoint' && context.references.endpoints[context.request.payload.parameters.outputs[0].destination.endpointId].trustScore <= 50",
    "type": "Expression"
  },
  "type": "Or"
}

Low-risk Bitcoin transaction

This condition matches operator-created Bitcoin transactions below 1 BTC where the account is not escalated and the destination is either an internal account or an endpoint with a trust score of 50 or higher.

{
  "left": {
    "expression": "context.references.users[context.request.author.id].roles.includes('operator') && context.request.payload.hasOwnProperty('parameters') && context.request.payload.parameters != null && context.request.payload.parameters.type == 'Bitcoin' && context.request.payload.parameters.outputs.reduce((sum, output) => sum + BigInt(output.amount), 0n) <= 100000000n && context.references.accounts[context.request.payload.accountId].metadata.customProperties.escalatePolicy != 'true' && context.request.payload.parameters.outputs[0].destination.type == 'Account'",
    "type": "Expression"
  },
  "right": {
    "expression": "context.references.users[context.request.author.id].roles.includes('operator') && context.request.payload.hasOwnProperty('parameters') && context.request.payload.parameters != null && context.request.payload.parameters.type == 'Bitcoin' && context.request.payload.parameters.outputs.reduce((sum, output) => sum + BigInt(output.amount), 0n) <= 100000000n && context.references.accounts[context.request.payload.accountId].metadata.customProperties.escalatePolicy != 'true' && context.request.payload.parameters.outputs[0].destination.type == 'Endpoint' && context.references.endpoints[context.request.payload.parameters.outputs[0].destination.endpointId].trustScore >= 50",
    "type": "Expression"
  },
  "type": "Or"
}

Create or update privileged users

This condition matches user creation or update intents when the target user has the admin or compliance role.

{
  "expression": "context.request.payload.roles.includes('admin') || context.request.payload.roles.includes('compliance')",
  "type": "Expression"
}

This condition matches user creation or update intents when the target user does not have either privileged role.

{
  "expression": "!context.request.payload.roles.includes('admin') && !context.request.payload.roles.includes('compliance')",
  "type": "Expression"
}

Avoid these patterns:

AvoidReason
fetch, imports, network calls, or database callsConditions cannot call external systems.
DOM, browser APIs, timers, or process APIsPolicy evaluation is server-side.
Statements that require a blockThe field expects an expression. Prefer array methods and logical operators.
Missing optional-field checksUndefined nested paths can make condition evaluation fail.
Comparing large amounts as normal numbersJavaScript numbers can lose precision.

Workflow objects

A workflow is an ordered array of approval steps. Each step must be satisfied before the next step is active.

TypeShapeUse when
RoleQuorum{ "role": "operator", "quorum": 2, "type": "RoleQuorum" }A number of users with one role must approve.
And{ "left": {...}, "right": {...}, "type": "And" }Multiple approval groups are required in the same step.
Or{ "left": {...}, "right": {...}, "type": "Or" }Either approval group can satisfy the step.

Example workflow:

{
  "workflow": [
    {
      "role": "transaction-operator",
      "quorum": 1,
      "type": "RoleQuorum"
    },
    {
      "left": {
        "role": "compliance",
        "quorum": 1,
        "type": "RoleQuorum"
      },
      "right": {
        "role": "risk",
        "quorum": 1,
        "type": "RoleQuorum"
      },
      "type": "And"
    }
  ]
}

Workflow rules:

  • For user-signed proposals, the intent submitter counts as the first approval.
  • For user-signed proposals, the submitter must have a role compatible with the first workflow step.
  • For system-signed proposals, the service submitter does not count as a user approval. If a system-signed policy includes a workflow, user-signed approvers must satisfy it.
  • If quorum is not reached, the step remains open and users can change their decision. Once a step is executed, it cannot be changed.
  • A user can count only once in the complete approval workflow.
  • Duplicate approvals are checked at public-key level.
  • If a user has roles on both sides of an And or Or, the engine uses one approval on the quickest valid path.
  • RoleQuorum.quorum must be at least 1.
  • And and Or use left and right, not arrays.
  • An omitted or null workflow auto-approves a matching intent.
  • An empty workflow array rejects a matching intent.

Workflow examples

Operator followed by supervisor or supervisor bot

Step 1 is satisfied by the submitter with the operator role. Step 2 is satisfied by one supervisor or one supervisor-bot.

[
  {
    "role": "operator",
    "quorum": 1,
    "type": "RoleQuorum"
  },
  {
    "left": {
      "role": "supervisor",
      "quorum": 1,
      "type": "RoleQuorum"
    },
    "right": {
      "role": "supervisor-bot",
      "quorum": 1,
      "type": "RoleQuorum"
    },
    "type": "Or"
  }
]

Operator, compliance or risk alternatives, then CISO

Step 1 is satisfied by the submitter with the operator role. Step 2 requires compliance or compliance-bot approval and risk or risk-bot approval. Step 3 requires one ciso approval.

[
  {
    "role": "operator",
    "quorum": 1,
    "type": "RoleQuorum"
  },
  {
    "left": {
      "left": {
        "role": "compliance",
        "quorum": 1,
        "type": "RoleQuorum"
      },
      "right": {
        "role": "compliance-bot",
        "quorum": 1,
        "type": "RoleQuorum"
      },
      "type": "Or"
    },
    "right": {
      "left": {
        "role": "risk",
        "quorum": 1,
        "type": "RoleQuorum"
      },
      "right": {
        "role": "risk-bot",
        "quorum": 1,
        "type": "RoleQuorum"
      },
      "type": "Or"
    },
    "type": "And"
  },
  {
    "role": "ciso",
    "quorum": 1,
    "type": "RoleQuorum"
  }
]

Role rules

Role names used in users, read access, conditions, and workflows must match exactly.

ValidAvoid
policy-operatorPolicyOperator
transaction-operatortransaction_operator
complianceCompliance Team
platform-adminplatform admin

The API schema accepts role names that match [a-z0-9\-]+.

Minimal create-policy example

{
  "id": "c35fa005-1640-42ee-a8a5-c43aaae6eb87",
  "alias": "operator-approval",
  "rank": 100,
  "scope": "Self",
  "intentTypes": ["v0_CreateTransactionOrder"],
  "scriptingEngine": "Javascript_v0",
  "condition": {
    "expression": "context.references.users[context.request.author.id].roles.includes('transaction-operator')",
    "type": "Expression"
  },
  "workflow": [
    {
      "role": "transaction-operator",
      "quorum": 2,
      "type": "RoleQuorum"
    }
  ],
  "lock": "Unlocked",
  "description": "Requires transaction operator quorum.",
  "customProperties": {},
  "type": "v0_CreatePolicy"
}

Common mistakes

MistakeResultFix
Empty workflow: []Matching intents are rejected.Omit workflow or use null for auto-approval, or define approval steps.
Omitted intentTypes on a high-rank policyPolicy can match unexpectedly broad workflows.Specify intent types unless the policy is a deliberate fallback.
customProperties: nullPayload validation can fail.Use {} when empty.
Role names with uppercase, underscores, spaces, or paddingWorkflows cannot be satisfied by expected roles.Use lowercase hyphenated role names, such as policy-operator.
And or Or written as arraysPayload validation fails.Use left, right, and type.
Amount compared as a JavaScript numberPrecision loss or mismatched comparisons.Use BigInt(...) and n literals.
Missing optional-field checksCondition can fail for intent types with different payload shapes.Check existence before accessing nested fields.
Required quorum has too few usersIntent cannot be approved.Create enough users before deploying the policy.
First workflow step excludes maker role for a user-signed policyIntent fails at submission or cannot progress.Include the maker role in the first workflow step. For system-signed policies, make sure user approvers can satisfy the workflow.

For implementation patterns, see Policy examples.