Controls

Approvals bound to parameters, explained

Most approval flows approve an intention, then execute a payload. Binding closes the gap: the approval covers the exact parameters, the record version and the policy version, or it stops being valid.

Here is an approval that approves nothing.

An agent proposes a supplier payment. A message arrives in Slack: “Agent requests approval to pay Acme Ltd, invoice INV-20418, 48,200.” The controller reads it, taps Approve, and goes back to her afternoon. The agent executes.

Between the tap and the execution, any of the following can be true without anybody noticing. The bank account on the supplier record changed that morning. The invoice was superseded by a credit note and the amount is now 4,820. A second approval request for the same invoice was already approved by somebody else. The agent’s own policy was relaxed an hour earlier and the payment no longer needed her at all.

She approved a sentence. The system executed a payload. Those are different objects, and in most approval flows nothing enforces that they refer to the same thing.

The gap between the check and the use

Engineers know this shape. A program checks a condition, then acts on it, and something changes in between. The check was true when it was made and false when it mattered.

Approval flows have the same structure and a much longer gap. The check is a person reading a summary. The use is a machine writing to a system of record. In between sits human latency — minutes, sometimes hours — during which the world carries on moving. Records get edited. Policies get changed. Other approvals land. Prices move.

For a human requester this mostly works out, because the human doing the work has context and hesitates when something looks off. An agent does not hesitate. It has an approval token and a task to finish.

What binding means

An approval is bound when it is cryptographically and logically tied to the exact thing that will be executed. Concretely, that means the approval record carries six things.

The exact parameters. Not a summary. The full payload that will be sent: payee identifier, bank account, amount, currency, value date, cost centre, posting period. Hashed, so the approval can be checked against what actually executed without storing the payload twice.

The record version. The version or revision identifier of every record the decision depends on — the invoice, the supplier master, the contract. If the supplier’s bank details are at version 7 when the approval is given, the approval is for version 7.

The policy version. Which controls were in force, at which thresholds, when the approval was requested. A control set that changes between request and execution invalidates the reasoning that led to the request.

The authority. Who approved, under which delegation, and the limit of that delegation. “Approved by the controller” is weaker than “approved by a named person holding authority up to 100,000 for this cost centre, delegation valid on that date”.

An expiry. Approvals go stale. A payment approval that is still executable a week later is a standing instruction that nobody thinks of as one.

Single use. The approval is consumed by the execution it authorised. A token that can authorise a second identical action is how one payment becomes two.

At execution, the system recomputes the hash of the payload it is about to send and compares it to the one in the approval. If they differ in any respect, execution stops. Not a warning. Not a log line. It stops.

What the approver should be looking at

Binding fixes what the approval covers. It does not fix what the approver understood, and those are separate failures.

An approval request worth reading shows the change rather than the request. What the record says now and what it will say afterwards. Which control caused this to need a person at all, and what its threshold is. What happens if it is wrong: whether the action is reversible, how, by whom, and within what window. And what was already automatic, so the person is not asked to rubber-stamp the parts the policy already decided.

The reversibility line is the one people skip and the one that changes behaviour. “This payment can be recalled within two hours by treasury” and “this payment cannot be recalled” deserve different amounts of attention, and a system that presents them identically is training its approvers to treat everything as routine.

When the parameters change

Binding forces an honest question: what happens when the payload no longer matches?

The lazy design is to let small differences through with a tolerance — the amount is within 1%, so proceed. This quietly converts a control into a guideline, and the tolerance is the first thing anybody clever will exploit.

The correct behaviour is to invalidate and re-request, with the difference shown explicitly. The second request should say what changed since the first approval and who changed it. That extra loop is the entire value: the case where the parameters changed between approval and execution is exactly the case a control exists to catch.

There is a related trap in the other direction. If re-approval is painful, people will approve wider things to avoid it — a blanket authority for the supplier, for the month, for the category. A blanket approval is not automatically wrong, but it has to be modelled honestly as what it is: a standing authority with a budget, an expiry, a scope and its own record, not a series of individual approvals that happen to be pre-signed.

Segregation of duties when one party is software

Segregation of duties assumes the requester and the approver are different people with different incentives. Introduce agents and the assumption needs restating, because “different agents” is not the same as “different parties” when both run under the same operating contract, on the same credentials, from the same policy.

Separation has to be established at the level of authority rather than at the level of identity. The agent that prepares a payment must not hold the capability to approve one. The approval path has to run through a channel the executing agent does not control. And the person in the loop has to be a person, which means resisting the very tempting shortcut of an approver agent that clears the queue overnight.

This is also why the approval record has to be written where the executing system cannot reach it. If the party taking the action is also the sole author of the record of having been authorised, the approval proves nothing to anybody outside the building. The same argument runs through what an evidence pack has to contain.

Fewer approvals, each of which means something

The honest objection to all of this is volume. Bound approvals with expiries and re-requests generate more interruptions, and an approver facing forty requests an hour approves all forty without reading any.

The answer is not to loosen the binding. It is to ask for far fewer approvals. Set the thresholds where the risk actually is. Let the routine population run under a control that blocks rather than asks. Batch the low-consequence work into a reviewed summary instead of individual taps. Reserve the interruption for actions that are irreversible, unusual or above a limit that means something.

An approval queue is a budget of attention. Spending it on everything is the same as spending it on nothing.

Meru Assure is built so that approvals bind to parameters, record versions and policy versions by default, and so that a mismatch stops execution rather than logging it. That is a design commitment rather than a claim about anybody’s deployment. The argument for it stands on its own: an approval that does not bind is a signature on a blank page.

All insights

The platform these arguments describe.

Ayion Meru runs agents on real work and proves what they did: controls on every consequential action, approvals bound to parameters, reconciliation against the system of record.