Enruta

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.

CheckWhenWhat is checkedOn failureStatus
M0Before the mandate is createdPolicy applicability: is there a signed policy that covers this agent, payee and category; is the agent paused; is a consent required and presentNo mandate; deny with no_matching_policy, agent_paused or consent_revokedLive
M1Before the credential is issuedPayee, category, time window, per-payment cap, cumulative budget (atomic reservation), duplicate and idempotency, screening; the approval thresholddeny or step_up; under a shadow policy, observeLive
M2Before the rail is calledThe credential is bound to the mandate; the amount within tolerance; the rail’s finality_class matches what the mandate was stamped withdenyBinding and finality stamping live; the lower cap for irreversible rails (finality_irreversible_cap) is enforced when the first irreversible rail adapter lands
M3When the rail asksAmount, currency and merchant against the mandate, answered inside the issuer’s two-second windowThe authorization is refused: amount_exceeds_mandate, merchant_mismatch, mandate_consumed, mandate_expiredLive; 44 ms in production
M4After fulfillment and settlementFulfillment confirmed, cleared amount, refunds, the merchant’s order, all joined per mandateAn exception with a suggested action on the reconciliation line, into the weekly reportLive

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

CheckIn the record
M0, M1decision.checks[], decision.reasons[], decision.risk
M2mandate, card_control_state, execution
M3rail_authorization_record with answered_in_ms
M4The settlement_record and reconciliation_line records chained to the payment