The task
A refund assistant returns a tool call. The schema is satisfied every time. Your job is to decide, in code, whether the call should be allowed to happen.
Here are four calls the model produced against the same schema. Exactly one of them is safe to execute.
// The schema every one of these satisfies.
{
"name": "issue_refund",
"input_schema": {
"type": "object",
"properties": {
"order_id": { "type": "string" },
"amount_cents": { "type": "integer", "minimum": 0 },
"reason": { "type": "string", "enum": ["damaged", "late", "unwanted"] },
},
"required": ["order_id", "amount_cents", "reason"],
"additionalProperties": false,
},
}
[
{ "order_id": "ORD-4417", "amount_cents": 2499, "reason": "damaged" },
{ "order_id": "ORD-4417", "amount_cents": 24990, "reason": "damaged" },
{ "order_id": "ORD-9999", "amount_cents": 2499, "reason": "late" },
{ "order_id": "ORD-4417", "amount_cents": 0, "reason": "unwanted" }
]
Starter material
The order the assistant was talking about:
{
"order_id": "ORD-4417",
"total_cents": 2499,
"placed_at": "2026-08-02",
"delivered_at": "2026-08-04",
"refunded_cents": 0,
"status": "delivered"
}
Your refund policy, which is not in the prompt and never should be:
- A refund cannot exceed what is left to refund on the order.
laterequires delivery more than seven days after the order.- A zero-value refund is not an action; it is a no-op that still emails the customer.
Write a function with this shape:
type Decision = { allow: true } | { allow: false; reason: string }
function check(call: RefundCall, order: Order): Decision {
// yours
}
What to check
Work through each call and say which rule catches it. Then write the checks.
- Does the order exist, and is it the one under discussion?
- Is the amount within what remains refundable?
- Does the stated reason hold against the order's own dates?
- Is the call a no-op?
Hints
The second call looks like a typo. Is that the right way to think about it?
No. 24990 is a well-formed integer that satisfies minimum: 0. Nothing about
it is malformed — it is simply ten times the order total. That is the whole
point of the exercise: a schema constrains shape, and shape was never the
problem. Your check compares against total_cents - refunded_cents, which is
data the model never saw.
Should the third call be rejected for the order ID or the reason?
Both, and the order matters. Check identity first and return before you
evaluate anything else: a reason check against an order you have not confirmed
exists is a check against undefined. This is the ordinary rule that
authorisation comes before business logic, and it is easy to lose when the
input arrives as a tool call rather than as a request body.
Is a zero-value refund really a failure?
It depends on your product, and that is the answer worth arriving at. It is not
a policy violation, so allow: false with a reason is defensible, and so is
allowing it and making the email conditional. What is not defensible is not
noticing: a call that does nothing but sends a message is the kind of thing that
reaches production because no rule named it.
Solution
One way to write it
function check(call: RefundCall, order: Order | undefined): Decision {
if (!order || order.order_id !== call.order_id) {
return { allow: false, reason: 'Unknown order' }
}
const remaining = order.total_cents - order.refunded_cents
if (call.amount_cents > remaining) {
return { allow: false, reason: `Exceeds ${remaining} remaining` }
}
if (call.amount_cents === 0) {
return { allow: false, reason: 'Zero-value refund is a no-op' }
}
if (call.reason === 'late') {
const days = daysBetween(order.placed_at, order.delivered_at)
if (days <= 7) return { allow: false, reason: `Delivered in ${days} days` }
}
return { allow: true }
}
Only the first call passes.
The part worth keeping is not the code. It is that every check reads data the
model never had access to, and none of them could have been expressed in the
schema. strict: true guarantees the call matches your schema exactly, and the
same documentation notes that a model may infer a value it was not given. Both
are true at once, which is why the shape being right tells you nothing about
the value being right.
Going further
- Add a check the model can act on. When you reject a call, return a tool execution error whose text states the rule and supplies the number, so the model can correct itself rather than guess again.
- Make the whole thing safe to call twice, then test it by calling it twice.