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`
The fix
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 verdictWith the fix, soma check accepts the program:
$ soma check right.cell
✓ All checks passed.