soma.cell

E0113: untrusted_value

E0113 untrusted_value — a model's answer or an HTTP reply decides a state, a URL, a file or a budget without a check.

A compile-time error of soma check, the checker of the Soma programming language (soma explain E0113).

The rule

A model's answer (think()) or an HTTP reply is untrusted: it may be stored, shown and computed with, but it may not decide the target of a transition(), a URL, a file path or set_budget until the handler tests it. In edition 2027 soma check refuses such a use with E0113; in edition 2026 the same program gets a warning under W0018 that says it becomes an error in 2027. Test the value first, with require contains([...], verdict) else BadVerdict or a match on the values you allow, so a prompt-injected answer cannot pick the state.

A program that triggers it

soma.toml

[package]
name = "support"
edition = "2027"

wrong.cell

cell Support {
    face {
        signal triage(ticket_id: String, text: String) -> String
    }
    state ticket {
        initial: open
        open -> escalated
        open -> closed
    }
    on triage(ticket_id: String, text: String) {
        let verdict = think("Answer escalated or closed for this ticket: {text}", map("max_tokens", 5))
        transition(ticket_id, verdict)
        return verdict
    }
}

What soma check prints:

$ soma check wrong.cell
error: `verdict` in transition() decides the state an instance moves to, and its value comes from think() with no check in between — whoever shapes that answer (a prompt-injected document, a remote server) chooses it; match it against the states you allow (`match next { "approved" -> transition(id, "approved")  _ -> fail("bad_state", next) }`) or `require contains([…], next) else BadState`
  --> wrong.cell:12:31
   |
12 |         transition(ticket_id, verdict)
   |                               ^
  = code: E0113
✗ 1 error(s), 0 warning(s).

The fix field of soma check --json: whoever shapes that answer (a prompt-injected document, a remote server) chooses it; match it against the states you allow (`match next { "approved" -> transition(id, "approved") _ -> fail("bad_state", next) }`) or `require contains([…], next) else BadState`

1 line is added after line 11.

wrong.cell → right.cell

--- wrong.cell
+++ right.cell
@@ -10,4 +10,5 @@
     on triage(ticket_id: String, text: String) {
         let verdict = think("Answer escalated or closed for this ticket: {text}", map("max_tokens", 5))
+        require contains(["escalated", "closed"], verdict) else BadVerdict "model answered {verdict}"
         transition(ticket_id, verdict)
         return verdict

With the fix, soma check accepts the program:

$ soma check right.cell
✓ All checks passed.