Decisions
Deny-first, with three outcomes. Only one of them gives you a receipt, and the other two are not failures.
allow- Settled. You get a receipt carrying the rail, its reference and the terms it settled under.
confirm- Accepted but not settled. Nothing has moved. A person has to approve it with a credential your agent does not hold.
deny- Refused, with reasons. It will stay refused until the request or the mandate changes.
What that looks like in the client
Money settles on a 200 and nowhere else. Everything that is not a settlement is raised as a typed error rather than returned as a half-receipt, so it is impossible to accidentally treat a pending approval as a completed payment.
try {
const receipt = await gl.pay(intent);
// Settled. receipt.reference is the rail's own settlement reference.
} catch (err) {
if (err instanceof ApprovalPendingError) {
// A person must approve. Nothing moved. Park the work and wait.
} else if (err instanceof DeniedError) {
// Refused. Read the reasons. Do NOT retry the same instruction.
} else if (err instanceof MandateExceededError) {
// A cap was hit. The mandate has to change, not the retry count.
}
}The typed errors are ApprovalPendingError, DeniedError, MandateExceededError, InsufficientFundsError, IdempotencyConflictError, ValidationError, RateLimitError, AuthError and ServerError. The client also carries PendingSettlementError for the optional holding band; the hosted server does not emit that problem today, so treat it as forward compatibility rather than something you will see.
Reading why
A decision carries reasons as prose for a person, and checks as the predicates that were actually evaluated, in evaluation order, with stable ids. Log the prose for humans and switch on the ids in code. An older decision may carry no checks at all, and an absent list is not the same as everything having passed.
The bug we expect you to hit
Why the answer is arithmetic
We do not ask a model whether a payment looks acceptable. The check is arithmetic against the mandate: what is left in the period, what this instruction would consume, whether this payee is on the list, whether the irreversible share is already used, whether the grant has expired. That makes the outcome deterministic, testable, and honestly explainable.
It is also why the gate is modelled formally rather than only tested: deny-first is the property the whole thing rests on, so it is checked as a model and not just exercised by examples.