Policy
The fence, written down
A policy here is not a document, and it is not an instruction in a prompt the agent can talk its way around. It is a few numbers a person sets once — a per-action limit, a hard ceiling, a daily cap and an optional list of recipients — and every request the agent makes is answered against them before anything happens. One function answers all of them, and it hands back one of three words with the figure that produced it.
The rules
Four numbers, and what happens when one is crossed.
The figures below are the ones a seeded project starts with. Two of them stop an action and ask a person. Two of them end it. Which is which is the whole of the design.
-
$50.00
Per-action limit
The most a single request may spend without a person seeing it. Set it to nothing at all and there is no per-action limit; set it to zero and every payment, down to one cent, needs a human.
require_approval Over it, the action stops where it stands, emails a named human and waits for their answer.
-
$1,000.00
Hard ceiling
The most any single request may ask for, full stop. It is checked before any of the rules that ask a person, so it can never be reached by way of an approval page.
deny Over it, the action is refused. No approval request is sent, and no person can approve past it.
-
$2,000.00
Daily cap
Measured against what has already executed today plus the amount being asked for now — never against the running total on its own. Summed per currency, per UTC day, and never converted.
deny If this request would carry the day past the cap, it is refused rather than held.
-
optional
Recipient allowlist
A list of accounts the agent may pay without asking. Leave it unset and every recipient is in scope. An empty list is not the same thing — it allows nobody.
require_approval A recipient off the list holds the action for a person. It is never a refusal on its own.
Order of evaluation
Every deny rule runs before any approval rule.
The order below is not a convenience. It is the enforcement. If a $12,000.00 payment to an account nobody recognises came back as needs approval, the hard ceiling would be worth exactly as much as the attention of the most tired person on the team at four in the afternoon — and a ceiling exists precisely so that nobody, tired or not, gets to move it. A hold is a question. A denial is an answer. Running all four denials first is what keeps the two apart.
deny rules — all four run first
-
Is there a policy for this action type at all?
no policy configured for action type "payment"
-
Is the policy that was found for this action type?
policy is for "payment", not "email"
-
Is the amount over the hard ceiling?
amount 120000 exceeds hard maximum 100000
-
Would today's spend plus this amount pass the daily cap?
daily cap 200000 would be exceeded (195000 already spent today)
require_approval rules — reached only if nothing denied
-
Does the policy ask for approval on every action of this type?
policy requires approval for all actions of this type
-
Is the amount over the per-action limit?
amount 50000 exceeds per-action limit 5000
-
Is the recipient off the allowlist?
recipient "acct_unknown" is not on the allowlist
allow
Nothing fired. The action executes immediately, and the reason
recorded against it is
within policy.
Reasons are written in integer cents, because that is what the money
path carries end to end — 50000 is
$500.00. There is exactly one place in the whole system where
dollars become cents, and it is in the demo agent, because the model
talks in dollars.
Try it
Ask for an amount. Watch which rule answers.
Every verdict on this page is computed in your browser by a port of the policy engine — same order, same comparisons, same reason strings. Nothing is sent anywhere, and no payment exists.
- Per action $50.00
- Hard ceiling $1,000.00
- Daily cap $2,000.00
- Spent today $37.50
- Allowlist acct_cloudhost, acct_mailgun
| Amount | Recipient | Decision | Reason | Load into the live row |
|---|---|---|---|---|
| allow | within policy | live |
Boundaries
The four that are easy to get wrong.
-
The limit is inclusive
Exactly $50.00 executes. Every comparison in the engine is a greater-than, never a greater-than-or-equal, so an amount that lands on a limit is inside it. $50.01 is the first amount that holds. The ceiling behaves the same way: exactly $1,000.00 is not denied — it is over the per-action limit, so it stops for a person, and $1,000.01 is the first amount refused.
-
Nothing set is not the same as zero
A limit left unset means no limit. A limit of
0means nothing is allowed. A per-action limit of zero holds every payment including a single cent; a hard ceiling of zero denies every one of them. The same trap sits in the allowlist: unset lets anyone through, and an empty list lets nobody through. Every check compares against null explicitly, because a plain truthiness test would read a zero as absent and quietly take the fence down. -
The cap is tested on spent plus this amount
Checking the running total on its own would let one request walk straight through a cap it started the day under. So the test is what the day would come to if this action executed. With $1,950.00 already executed today, a $75.00 request is refused outright — not held, because the cap is a deny rule and deny rules run first:
-
The cap is per currency and never converts
Today's spend is summed for the currency in the request, so a project with a $2,000.00 cap has one cap in US dollars and another, independent one in euros. Two currencies mean two fences, not one shared between them. That is a known limitation of this build, written down here so it is not mistaken for a bug and quietly fixed into something the daily cap was never measured against.
Why it is testable
One function, no I/O
The engine takes the request, the policy and the day's spend as arguments. It reads no clock, no database and no network, and it mutates nothing it is given. That is why the suite behind it can be exhaustive rather than representative — every rule, every boundary and every reason string is pinned by a test, with no fixtures and no fake timers.