Schema-valid agent output still needs an authority check

Structured output from a model usually gets checked once: does it parse, and does it match the schema. If both pass, the object goes downstream as if it were trusted data. That check is necessary and it is nowhere near enough. A transfer object can be perfectly valid JSON, match every type and enum, and still name an account the requester cannot touch, or carry an amount in the wrong unit.
Where the schema stops
JSON Schema 2020-12 is good at shape. required, enum, maxLength, additionalProperties: false and bounded arrays make the object narrow and predictable, and constrained generation makes it more likely the model produces that shape on the first try. None of those keywords can say whether a referenced record exists, whether a date follows an approval, or whether this user may act on that target.
So the useful split is by question. Serialization: is it the right shape? Vocabulary: are the codes ones the consumer understands? Business invariants: is the amount positive, does the end date come after the start? References and evidence: does source_id point at something real? Authority: may this requester do this to this resource? Each one has a different owner and runs at a different point, and the authority check belongs right before the side effect, against current state, not at parse time.
The MCP tools specification has the same shape at the protocol boundary. A tool can declare an outputSchema; servers MUST return structured results that conform to it, and clients SHOULD validate against it. That covers the shape of the call. It says nothing about whether the call should happen.
Keep the model's job small
If the runtime already knows the customer ID, the correlation ID and the current time, inject them after generation instead of asking the model to produce them. A model asked for a timestamp will produce a plausible one. Fields should only hold what the producer can actually establish.
Same idea for prose. Put the human-readable explanation in its own field and the machine decision in an enum next to it. Otherwise someone eventually writes a regex over the explanation to recover the decision, and a copy change breaks control flow.
Repair is for formatting, never for meaning
When validation fails, there is a strong pull toward fixing the object and moving on. That is fine for defects with exactly one correct answer: stripping a markdown fence around the JSON, normalizing a known date format, filling a runtime-owned ID. It is not fine for an amount, a recipient, an authority decision or a missing citation. Guessing those turns a visible failure into silent damage.
Retrying generation with the validation errors attached is reasonable for producer defects, with a hard attempt limit. Without one, each retry costs money and can produce a different invalid answer, which makes the original failure harder to diagnose. Authorization denials and stale preconditions should not be retried at all.
What does not get repaired goes to a dead-letter store with the contract version, the validation report, the attempt history and an owner, but not the raw prompt in a widely readable queue. A corrected payload is a new record linked to the rejected one.
Version the meaning, not just the fields
Renaming a field is obviously breaking. Changing a unit while keeping the type is worse, because nothing fails loudly. Adding an enum value looks additive and breaks every consumer with an exhaustive switch. A wording change in the field description changes no wire format but can shift what the model emits, so it still needs an eval run.
It depends on how many consumers there are, but in general it pays to record four versions per output: schema, generation instructions, validators and consumer mapping. When rejections spike, that is how you tell model drift from a tool release.
One metric worth watching specifically: first-pass schema validity going up while domain rejections also go up. That pattern usually means the model learned the shape and not the rules.
The longer structured output contracts guide on Edilec has the full validation-layer table, a compatibility matrix for each change type, and notes on contract testing and ownership.
Disclosure: AI assistance was used to draft this excerpt. The linked Edilec article, the JSON Schema 2020-12 specification and the MCP tools specification are the public technical sources; this excerpt does not describe a client deployment or measured Edilec result.




