Docs · Guides
Policy
Deterministic rules that decide ALLOW, DENY or REQUIRE_APPROVAL — versioned, simulated, then activated.
The document
A policy is a JSON document with a spec version and an ordered set of rules. Each rule names an effect and, optionally, a condition; a rule with no condition always matches.
{
"spec_version": "bengar-policy/1",
"rules": [
{
"id": "office-supplies-under-5k",
"effect": "ALLOW",
"when": {
"op": "and",
"args": [
{ "op": "matches_resource", "input": "resource",
"value": "resource://company/procurement/*" },
{ "op": "lte", "input": "amount.minor", "value": 500000, "currency": "TRY" }
]
}
},
{
"id": "large-spend-needs-a-person",
"effect": "REQUIRE_APPROVAL",
"when": { "op": "gt", "input": "amount.minor", "value": 500000, "currency": "TRY" }
}
]
}Effects, and how they compose
Three effects: ALLOW, DENY and REQUIRE_APPROVAL. When several rules match, the result is their meet — the most restrictive wins, and DENY is absorbing. The identity is ALLOW, so a document where nothing matched restricts nothing; the Gateway treats “no rules matched” as its own reason code rather than as an allow.
REQUIRE_APPROVAL is not an allow. It is its own value, never an ALLOW carrying a flag, so a caller that treats “not DENY” as go has to make that mistake deliberately.
Inputs a rule may read
| Input | Type |
|---|---|
| action | string |
| resource | resource |
| agent | did |
| amount.minor | money — minor units, compared against a currency |
| amount.currency | string |
| request_time | timestamp |
| delegation_depth | integer |
| permit_commitment | string |
| backend_finality | string |
An input that no milestone produces yet — risk_score today — is refused at validation with the milestone named, rather than accepted and silently unavailable. A policy referencing a value nobody supplies would deny everything, and its author would have no way to see why.
Operators
eq,neq— equalitylt,lte,gt,gte— comparison, and for money the currency must matchbetween— a closed rangein,not_in— membershipmatches_resource— resource patternswithin_time_window— from and untiland,or,not— composition
Money comparisons are in minor units, as integers. A limit a rounding error can cross is not a limit, so nothing here parses a decimal.
Determinism
The same inputs against the same document always produce the same decision, and the evaluation reports which rules matched and how many steps it took. Nothing here is a model output, and there is no scoring that varies between runs.
Versioning, simulation and activation
- Save a version with
PUT /v1/projects/:id/policy. Saving does not activate it. - Simulate against it — the simulation authorizes nothing, spends no counter and writes no audit event.
- Activate with
POST .../policy/activate. That is the moment it starts deciding, and it is audited whether it succeeds or is refused.
AVAILABLE Publish a policy version — /projects/:id/policies
Where: Policies → Author a policy
First: POLICY_AUTHOR, which an administrator can now grant.
Requires: POLICY_AUTHOR
AVAILABLE Simulate a decision against the policy — /projects/:id/policies
Where: Policies → Simulate
First: An active policy version and at least one registered agent.
Requires: ADMIN, POLICY_AUTHOR, AUDITOR
AVAILABLE Activate a policy version — /projects/:id/policies
Where: Policies → Activate
First: An inactive version to activate.
Requires: POLICY_AUTHOR
Separation of duties
policy.publish and policy.activate are POLICY_AUTHOR. ADMIN does not acquire them by being ADMIN — an administrator who could author the rules and also approve under them would defeat the arrangement the rules exist to create.