Policy format
A policy is a JSON document with an ordered list of rules. This page describes every field, how the rules are evaluated, and what the console refuses when you save. For a guided introduction, see Write a policy.
Document shape
{
"rules": [
{
"id": "large-orders",
"match": {
"agents": ["<agent id>"],
"tools": ["erp.purchase_orders.create"],
"environments": ["<environment id>"],
"args": [{ "path": "max_total", "op": "gt", "value": 1000 }]
},
"effect": "require_approval",
"approvers": "staff"
}
]
}
Parsing is strict:
- Field names are exactly as shown, in lowercase with underscores. An unknown field anywhere is an error.
- Effects, operators and approvers are strings. Numbers in their place are an error.
rulesis required. It may be empty, which denies everything.
Each environment has its own policy, saved in versions. See how versions work.
Rule fields
| Field | Required | Type | Meaning |
|---|---|---|---|
id | Yes | string | A name for the rule, unique in the document. It appears in denials, approval requests and the audit trail, so make it meaningful. |
match | Yes | object | Which calls the rule applies to. {} matches every call. |
effect | Yes | string | allow, deny or require_approval. |
approvers | No | string | Who decides a require_approval call: staff, customer or both. Only allowed on require_approval rules. |
Match fields
All match fields are optional. A call matches the rule only if it matches every field that is present.
| Field | Type | Matches when |
|---|---|---|
agents | array of strings | The calling agent's id is in the list. Ids are shown on Tools & agents → Agents. |
tools | array of strings | The tool id is in the list, for example erp.purchase_orders.create. Tool ids are shown on Tools & agents → Tools. |
environments | array of strings | The environment's id is in the list. Each environment has its own policy, so you rarely need this. |
args | array of conditions | Every condition holds for the call's arguments. See below. |
Lists compare exact, case-sensitive strings; there are no wildcards or patterns. A missing field (or null) matches anything. An empty list ([]) matches nothing, so a rule with "agents": [] never applies.
Argument conditions
Each entry in args is an object:
| Field | Type | Meaning |
|---|---|---|
path | string | Where the value is in the arguments. |
op | string | The comparison: eq, in, gt, gte, lt or lte. |
value | depends on op | What to compare with. |
Paths
A path is a list of names separated by dots, for example max_total or delivery.country. A segment that is a number picks an item of an array, counting from 0: lines.0.quantity is the quantity of the first order line. Empty segments, such as a..b or a trailing dot, are refused when you save.
Arguments are named after the API's own fields; see OpenAPI extensions. Conditions see the arguments after Gatelion has checked and normalized them: text is trimmed and in Unicode NFC, and decimals are exact.
Operators
op | Console shows | value must be | True when |
|---|---|---|---|
eq | is | a string, number, true, false or null | The argument equals the value. |
in | is one of | an array of such values | The argument equals any item in the array. |
gt | > | a number, or a string holding a decimal | The argument is greater than the value. |
gte | ≥ | a number, or a string holding a decimal | The argument is greater than or equal to the value. |
lt | < | a number, or a string holding a decimal | The argument is less than the value. |
lte | ≤ | a number, or a string holding a decimal | The argument is less than or equal to the value. |
How values compare:
gt,gte,ltandltecompare exact decimals. The argument may be a number or a decimal string such as"1500.25", which is how decimal fields arrive.eqwith a number value compares numerically, so{"op": "eq", "value": 1500}matches the decimal"1500.00". Prefer a number value for decimal fields. If the argument isn't a number or a decimal string, the condition can't be checked (see below).eqwith a string value compares the exact text, case-sensitively. It never equals a number argument:"5"doesn't match the integer5.eqwithtrue,falseornullmatches only that value.inapplies the same rules to each item.
A decimal string is plain digits with an optional minus sign and one decimal point: "1000", "-5", "1000.50". Exponents, plus signs and thousands separators aren't decimals.
When a condition can't be checked
A condition can't be checked when the path doesn't exist in the arguments, or when a numeric comparison (gt, gte, lt, lte, or eq and in with number values) meets a value that isn't a number. Such a condition fails closed:
- In an
allowrule, the rule doesn't match, and evaluation goes on with the next rule. - In a
denyorrequire_approvalrule, the rule matches.
A condition that can be checked and is false never matches, whatever the effect. With several conditions, one false condition means the rule doesn't match.
Effects
effect | What happens to a matching call |
|---|---|
| Allow | It runs at once, and the agent gets the result. |
| Deny | It's refused with policy_denied, and the message names the rule. Nothing is sent. |
| Needs approval | It waits for a decision, and the agent gets pending_approval with an execution id. |
Approvers
approvers is only allowed on require_approval rules.
approvers | Who decides |
|---|---|
staff (or left out) | One approver of the environment, subject to separation of duties. |
customer | The customer the outside agent acts for, on a confirmation page after signing in with your login. Your staff can't decide. |
both | The customer and one approver, in either order. |
With customer or both, a rejection by anyone rejects the request. A call by one of your own agents that matches a customer or both rule is denied with customer_confirmation_unavailable, because there's no customer to ask.
Evaluation order
For each call, the Gateway checks the rules from the first to the last:
- A rule is skipped if
agents,toolsorenvironmentsis present and doesn't contain the call's agent, tool or environment. - Otherwise the
argsconditions are checked, failing closed as described above. - The first rule that matches decides the call.
- If no rule matches, the call is denied with the message "No rule matched (default deny)".
Rule order is the only priority there is. A deny placed after an allow that matches the same call is never reached.
Which tools an agent sees
An agent's tool list contains only the tools it could possibly run. For each enabled tool, Gatelion walks the rules that apply to this agent and tool, in order:
- The first such rule without argument conditions decides: the tool is visible unless that rule is
deny. - Before that, an
alloworrequire_approvalrule with argument conditions makes the tool visible, because some arguments could pass. - A
denyrule with argument conditions doesn't decide; the walk goes on. - If no rule applies, the tool is hidden.
A hidden tool answers unknown_tool if the agent calls it anyway. Visibility never authorizes a call; every call is evaluated in full.
Validation errors on save
The console checks the document twice. Nothing is saved while either check fails.
In the editor's JSON tab, these stop you from going back to Rules or clicking Review and save:
| Message | Cause |
|---|---|
| This isn't valid JSON. Check brackets, quotes and commas. | The text isn't JSON. |
| Each rule needs an id, a match and an effect (allow, deny or require_approval). | rules isn't a list, or a rule lacks a text id, a match object or a known effect. |
When you save, Gatelion checks the document in full and refuses it with one of these:
| Message | Cause |
|---|---|
| Policy is not valid: … | An unknown field, a missing required field, a wrong type, or an effect, operator or approvers value that doesn't exist. The rest of the message points to the place. |
| Rule ids must be non-empty and unique (…) | A rule has an empty id, or two rules share one. |
| Rule "…": approvers only applies to require_approval rules. | approvers is set on an allow or deny rule. |
| Rule "…": invalid argument path "…". | A path is empty or has an empty segment. |
| Rule "…": value for "…" on "…" has the wrong type. | For example, in without an array, eq with an object or array, or gt with text that isn't a decimal. |
Saving doesn't check that agent, tool or environment ids exist. The editor warns about tool and agent ids it doesn't know, but a misspelled id still saves and never matches, so check rules that don't seem to apply.
A full example
A purchasing setup in an ERP, plus a flight-booking portal used by customers' own agents:
{
"rules": [
{
"id": "purchasing-agent-suppliers",
"match": {
"agents": ["<id of the purchasing agent>"],
"tools": ["erp.suppliers.list"]
},
"effect": "allow"
},
{
"id": "reads",
"match": { "tools": ["erp.inventory.list", "erp.purchase_orders.get"] },
"effect": "allow"
},
{
"id": "no-cancelling",
"match": { "tools": ["erp.purchase_orders.cancel"] },
"effect": "deny"
},
{
"id": "large-orders",
"match": {
"tools": ["erp.purchase_orders.create"],
"args": [{ "path": "max_total", "op": "gt", "value": 1000 }]
},
"effect": "require_approval"
},
{
"id": "orders-main-warehouses",
"match": {
"tools": ["erp.purchase_orders.create"],
"args": [{ "path": "warehouse", "op": "in", "value": ["WH-1", "WH-2"] }]
},
"effect": "allow"
},
{
"id": "flight-search",
"match": { "tools": ["portal.flights.search"] },
"effect": "allow"
},
{
"id": "expensive-bookings",
"match": {
"tools": ["portal.bookings.create"],
"args": [{ "path": "total_price", "op": "gte", "value": 2000 }]
},
"effect": "require_approval",
"approvers": "both"
},
{
"id": "bookings",
"match": { "tools": ["portal.bookings.create"] },
"effect": "require_approval",
"approvers": "customer"
}
]
}
What it does:
- Only the purchasing agent may list suppliers. Every agent may read stock and orders.
- No agent may cancel orders, and the cancel tool is hidden.
- Orders up to 1000 for warehouses
WH-1andWH-2run at once. Orders above 1000 wait for an approver. Orders for any other warehouse match no rule and are denied. - Customers' agents may search flights freely. Every booking waits for the customer's confirmation, and bookings of 2000 or more also need one of your approvers.
expensive-bookingscomes first; placed afterbookings, it would never be reached. - If one of your own agents tried to book, it would be denied with
customer_confirmation_unavailable.
Still stuck? Contact support from the console.