The model is a caller that hallucinates

Your Rails app has its first caller that fabricates perfectly shaped, completely wrong input. Validate accordingly.

Every caller your Rails app has ever had was either honest or malicious. Users make typos. Attackers craft payloads. You validate for both, and Rails gives you the tools.

A language model is a third kind of caller: it fabricates perfectly shaped, completely wrong input, with total confidence, at random.

It will call your lookup tool with an order number that matches your format exactly and does not exist. It will return JSON that passes your schema check and describes a refund you never discussed. Shape-based validation, the kind strong params taught us, is defeated by a caller whose specialty is producing the right shape with the wrong content.

The fix is validating meaning, not just shape:

class DraftValidator
  def valid?(draft, ticket)
    return false unless draft[:order_number].nil? ||
      ticket.customer.orders.exists?(number: draft[:order_number])
    return false if draft[:promised_refund] && !ticket.refund_approved?
    return false if draft[:body].match?(FORBIDDEN_CLAIMS)
    true
  end
end

Three principles:

  1. Verify every reference against the database. If the model mentions an order, look it up. If it cites a policy, check the policy exists and says what the model claims.
  2. Whitelist actions, never trust intent. The model does not decide whether a refund is possible. Your code checks refund_approved?. The model only drafts words around facts your application established.
  3. Treat unvalidated model output like user input from an account you suspect is compromised. Because functionally, that is what it is.

This is not exotic engineering. It is the same defensive posture Rails developers already apply to params, aimed at a new caller.

The series so far: the boring stack, no model calls in requests, tools as service objects.