Docs · Guides
Five checks M0 to M4
LiveUpdated
A policy is enforced twice by design: Enruta decides, then the rail’s own control decides again. Spelled out, that is five checks, each with a moment, a question and a failure action. The fifth, after settlement, is the one no agent-payment protocol writes down.
| Check | When | What is checked | On failure | Status |
|---|---|---|---|---|
| M0 | Before the mandate is created | Policy applicability: is there a signed policy that covers this agent, payee and category; is the agent paused; is a consent required and present | No mandate; deny with no_matching_policy, agent_paused or consent_revoked | Live |
| M1 | Before the credential is issued | Payee, category, time window, per-payment cap, cumulative budget (atomic reservation), duplicate and idempotency, screening; the approval threshold | deny or step_up; under a shadow policy, observe | Live |
| M2 | Before the rail is called | The credential is bound to the mandate; the amount within tolerance; the rail’s finality_class matches what the mandate was stamped with | deny | Binding and finality stamping live; the lower cap for irreversible rails (finality_irreversible_cap) is enforced when the first irreversible rail adapter lands |
| M3 | When the rail asks | Amount, currency and merchant against the mandate, answered inside the issuer’s two-second window | The authorization is refused: amount_exceeds_mandate, merchant_mismatch, mandate_consumed, mandate_expired | Live; 44 ms in production |
| M4 | After fulfillment and settlement | Fulfillment confirmed, cleared amount, refunds, the merchant’s order, all joined per mandate | An exception with a suggested action on the reconciliation line, into the weekly report | Live |
Beyond the five there is the observe state (shadow mode): the checks run, the result is recorded, nothing is enforced.
Two failures, two checks
Ten agents each request $100 with $500 left in the month. The only authority on the budget is M1’s atomic reservation (UPDATE budgets … WHERE reserved + spent + amount ≤ cap): the sixth mandate cannot be issued, and the remaining five are denied with over_budget. M3 catches a different class of error: a single-use card presented at another merchant, or a merchant trying to clear $120 on a $100 card, is refused by the rail with merchant_mismatch or amount_exceeds_mandate. Together they are the answer to “you budgeted $100 and spent $1,000”.
Where each check is recorded
| Check | In the record |
|---|---|
| M0, M1 | decision.checks[], decision.reasons[], decision.risk |
| M2 | mandate, card_control_state, execution |
| M3 | rail_authorization_record with answered_in_ms |
| M4 | The settlement_record and reconciliation_line records chained to the payment |