Ratifia
← All posts

What belongs in an approval context payload

A reviewer who clicks approve without understanding the decision is a liability, not a control. Five things every approval request should carry — and three it should never.

Earnest Berry 4 min read approvalsdesigncontext

An approval button is a control only if the person pressing it understood what they were approving. Otherwise it is a liability generator: it produces an audit record asserting that a human reviewed something, when in fact a human dismissed a notification.

The difference between those two outcomes is entirely determined by what you put in front of the reviewer. So it is worth being precise about it.

The test#

Before adding a field to an approval payload, ask: would a reviewer decide differently if this were absent?

If no, it is decoration. Decoration is not free — it costs attention, and attention is the scarce resource in a queue of forty pending approvals. Every field that does not change the decision makes the fields that do harder to find.

That test kills more than you would expect. Workflow run IDs, internal trace links, the model's token count, the queue name — all real, all logged elsewhere, none of them changing whether a human approves a $12,400 refund.

The five that survive#

1. The action, stated as an action#

Not "review request #48213." The reviewer needs the verb and the object: issue a $12,400 refund to account 8841. Write it the way a person would say it out loud. If the action cannot be stated in one sentence, the approval is scoped wrong and you should be gating something smaller.

2. The exact operation to be performed#

The literal tool call, with its literal arguments:

{
  "tool": "issueRefund",
  "args": { "account": "8841", "amount": 12400, "currency": "USD" }
}

This is the thing that will actually execute. Not a summary of it, not a rendering of it — it. A reviewer who approves a prose description and gets a different call has approved nothing, and your audit trail records a consent that was never given.

3. The state delta#

What is true before, what is true after:

- account_balance: $41,900
+ account_balance: $29,500

Reviewers reason about consequences, not operations. issueRefund(12400) is an operation. "The account drops below the $30,000 covenant floor" is a consequence, and it is the one that changes the answer.

4. The model's reasoning, marked as the model's#

Include it. Label it. Never let it render like a system-generated fact.

An AI recommendation is evidence, and the reviewer should weigh it as such. The failure mode here is automation bias: a confident "Approve — policy match" in neutral typography reads as a verdict rather than a suggestion, and reviewers rubber-stamp it. Attribute it to the model, name the model, and put it below the delta — never above.

5. Why this stopped#

Which policy fired, and on what clause. payouts-over-5k · amount exceeds $5,000 threshold.

Reviewers who understand why a decision reached them make better decisions, and they escalate the right ones. Reviewers who see approvals arrive for no legible reason learn to treat the queue as noise. That learning is permanent, and it is how an approval system decays into a rubber stamp within a quarter.

The three that must never appear#

Credentials, tokens, or keys. Approval payloads get forwarded to Slack, rendered in email, and retained for years under compliance holds. They are the least appropriate place in your system for a secret.

Anything you cannot show in an audit. If a field would be redacted from the record a regulator reads, it should not be in the record the reviewer reads either. The whole value of the payload is that the reviewer's view and the auditor's view are the same view. Divergence there means nobody can reconstruct the decision.

Unbounded data. A 40,000-row export is not context. It is an invitation to approve without looking, and it will be the exhibit in the postmortem. Summarize, link out, and let the reviewer pull the detail if they want it.

Why this is not a form#

The instinct is to define a schema — amount, account, reason — and render it as a table. It works for one workflow and collapses at the second, because a refund approval and a content-moderation approval and a model-deployment approval share almost no fields.

What they share is a shape: a headline action, an operation, a consequence, evidence, and a reason for stopping. So the payload should be a list of typed blocks — text, diff, code, table, key-value — that a workflow composes as it needs. The system renders blocks; the workflow decides which blocks the decision requires.

That is why Ratifia takes a free-form context payload rather than a fixed approval schema. The engines you already run can tell a reviewer that something is waiting. What they cannot tell them is what it means, because they were never given a place to put it.