Before you start
The agent and budget below come from the quickstart. Export the two ids it printed, and scope the agent forrefund.create so its key may ask about refunds at all:
Identifiers
Why a cap cannot express this
per_transaction_cap_cents and cost_cap compare cost_cents to a number you wrote when you
saved the policy. That number cannot be “whatever this order was worth”, so the tightest cap
you can write is the largest order you ever expect, which is far too loose for the smallest one.
A condition predicate can compare a field to another field on the same request instead of to a
literal, using value_field:
value_field accepts exactly the names the left side
accepts: action_type, resource, cost_cents, currency, agent_id, agent_mode,
budget_id, or any metadata.* dot path. The condition
language covers the rest of the grammar.
1. Write the ceiling
201 Created
action_types is an exact list, not a prefix. To cover a family of refund types, drop it and
match action_type with starts_with inside the expression instead.
2. Refuse a refund that arrives without an order total
A field that is not on the request resolves to nil, and nil does not compare. It does not fall back to zero. So a refund that omitsmetadata.order_total_cents makes the predicate false, the
rule does not fire, and the refund goes through.
Add the guard by sending the full rules array back. An update replaces the array rather than
merging into it, and each rule keeps its id only if you send the id back with it:
200 OK
id is assigned a fresh one, and a decision recorded last week
then names a rule id that no longer exists in the policy. Keep the ids.
3. Send the order total with the refund
The agent asks before it refunds, and carries the order total it is refunding against:201 Created
complete or fail once your rail has answered.
4. Watch it refuse
Same order, an amount above what the order was worth:403 Forbidden
detail.matched is the predicate that fired, echoed back with the value_field intact, so the
record of the denial says which two numbers were compared rather than just naming a rule.
What write time catches
Both mistakes are refused when you save the policy, not when an agent is waiting on an answer:422 Unprocessable Entity
/control/v1/agent/actions. The payment endpoint always evaluates as payment.create,
whatever you send it, so it cannot carry a refund.create action type.
Next steps
Pass facts from your own service
Where
metadata.order_total_cents comes from, and what else belongs there.Roll a policy out safely
Preview this rule before it ever decides anything real.
Writing a policy
Every rule kind, operator, and combinator.
Errors
Every rule that can appear in
decision.rule.