{
 "description": "Mistakes models make writing Soma, each with the real compiler diagnostic and the fix. Verified against the soma binary.",
 "gotchas": [
  {
   "n": 1,
   "title": "Nested string literals inside `{...}` interpolation",
   "markdown": "```soma\n// WRONG — a string literal inside an interpolation segment\nreturn \"len: {len(\\\"hi\\\")}\"\n// error: string interpolation cannot evaluate a nested string literal\n//        in '{len(\"hi\")}' — bind the value with a let first\n```\n```soma\n// RIGHT — bind it, then interpolate the variable\nlet n = len(\"hi\")\nreturn \"len: {n}\"\n```",
   "wrong": "// WRONG — a string literal inside an interpolation segment\nreturn \"len: {len(\\\"hi\\\")}\"\n// error: string interpolation cannot evaluate a nested string literal\n//        in '{len(\"hi\")}' — bind the value with a let first",
   "right": "// RIGHT — bind it, then interpolate the variable\nlet n = len(\"hi\")\nreturn \"len: {n}\"",
   "diagnostic": "error: string interpolation cannot evaluate a nested string literal"
  },
  {
   "n": 2,
   "title": "`match` arms use `->`, not `=>`",
   "markdown": "```soma\nreturn match x { 1 => \"a\"  * => \"b\" }\n// error: match arms use '->', not '=>'\n```\n```soma\nreturn match x { 1 -> \"a\"  * -> \"b\" }\n```\n`=>` is **lambda** syntax (`p => p + 1`). `->` is match arms and signal\nreturn types. Don't cross them.",
   "wrong": "return match x { 1 => \"a\"  * => \"b\" }\n// error: match arms use '->', not '=>'",
   "right": "return match x { 1 -> \"a\"  * -> \"b\" }",
   "diagnostic": "error: match arms use '->', not '=>'"
  },
  {
   "n": 3,
   "title": "Handlers don't declare return types",
   "markdown": "```soma\non add(a: Int, b: Int) -> Int { return a + b }\n// error: handlers do not declare return types — put '-> Int' on the\n//        signal declaration inside face { }\n```\n```soma\nface { signal add(a: Int, b: Int) -> Int }\non add(a: Int, b: Int) { return a + b }\n```",
   "wrong": "on add(a: Int, b: Int) -> Int { return a + b }\n// error: handlers do not declare return types — put '-> Int' on the\n//        signal declaration inside face { }",
   "right": "face { signal add(a: Int, b: Int) -> Int }\non add(a: Int, b: Int) { return a + b }",
   "diagnostic": "error: handlers do not declare return types — put '-> Int' on the"
  },
  {
   "n": 4,
   "title": "Adjacent string literals do NOT concatenate",
   "markdown": "```soma\nreturn \"hello \" \"world\"\n// error: in G.hello: a string literal follows `return` and is never\n//        evaluated — adjacent string literals do not concatenate\n```\n```soma\nreturn \"hello world\"        // one literal\nlet name = \"world\"\nreturn \"hello {name}\"       // or interpolate\n```\n(`soma check` also warns on any other unreachable statement after\n`return` / `break` / `continue`.)",
   "wrong": "return \"hello \" \"world\"\n// error: in G.hello: a string literal follows `return` and is never\n//        evaluated — adjacent string literals do not concatenate",
   "right": "return \"hello world\"        // one literal\nlet name = \"world\"\nreturn \"hello {name}\"       // or interpolate",
   "diagnostic": "error: in G.hello: a string literal follows `return` and is never"
  },
  {
   "n": 5,
   "title": "`==` IS structural on lists, maps and variants",
   "markdown": "```soma\n[1, 2] == [1, 2]                                  // true\nmap(\"US\", 1, \"EU\", 2) == map(\"EU\", 2, \"US\", 1)    // true — key order is irrelevant\n[] == []                                          // true\n```\n`<`, `>` on lists or maps is an error (compare a field or `len()`), and values\nof different kinds are an error to compare (`1 == \"1\"`), not `false`.",
   "wrong": "[1, 2] == [1, 2]                                  // true\nmap(\"US\", 1, \"EU\", 2) == map(\"EU\", 2, \"US\", 1)    // true — key order is irrelevant\n[] == []                                          // true"
  },
  {
   "n": 6,
   "title": "Float equality needs a tolerance",
   "markdown": "```soma\nreturn 0.1 + 0.2 == 0.3      // false — floating point\n```\n```soma\nreturn abs((0.1 + 0.2) - 0.3) < 0.0001\n```",
   "wrong": "return 0.1 + 0.2 == 0.3      // false — floating point",
   "right": "return abs((0.1 + 0.2) - 0.3) < 0.0001"
  },
  {
   "n": 7,
   "title": "`transition()` returns a map, not the target string",
   "markdown": "```soma\non advance(id: String) {\n    return transition(id, \"next\")   // returns {id, from, to}, not \"next\"\n}\n```\n```soma\non advance(id: String) {\n    transition(id, \"next\")\n    return get_status(id)           // the new state as a string\n}\n```\nGuard fallible transitions with `try`:\n```soma\nlet r = try { transition(id, \"next\") }\nif r.error != () { return map(\"error\", r.error) }\n```",
   "wrong": "on advance(id: String) {\n    return transition(id, \"next\")   // returns {id, from, to}, not \"next\"\n}",
   "right": "on advance(id: String) {\n    transition(id, \"next\")\n    return get_status(id)           // the new state as a string\n}"
  },
  {
   "n": 8,
   "title": "`is_a` does not recognize sum-type VARIANTS — match them",
   "markdown": "```soma\nlet b = Box { w: 3 }         // Box is a `variants` constructor\nreturn is_a(b, \"Box\")        // false — variants aren't tagged records\n```\n```soma\n// extract the kind with an exhaustive match handler\non kind(s: Map) {\n    return match s {\n        Box { w } -> \"Box\"\n        // ... every variant\n    }\n}\n```\n(`is_a` / `is_type` DO work on record literals: `is_a(Game { x: 1 }, \"Game\")` is `true`.)",
   "wrong": "let b = Box { w: 3 }         // Box is a `variants` constructor\nreturn is_a(b, \"Box\")        // false — variants aren't tagged records",
   "right": "// extract the kind with an exhaustive match handler\non kind(s: Map) {\n    return match s {\n        Box { w } -> \"Box\"\n        // ... every variant\n    }\n}"
  },
  {
   "n": 9,
   "title": "There is no `cell type X { fields { ... } }`",
   "markdown": "Records are plain map-shaped values. Construct them with a literal:\n```soma\nlet g = Game { bet: 10, pot: 0 }   // a field-accessible value (a Map)\ng.bet = 20                          // mutate fields in place\nlet b = g.bet\nreturn is_a(g, \"Game\")              // true — record literals carry _type\n```\nUse `cell type X { variants { ... } }` only for *sum types* (tagged unions).",
   "wrong": "let g = Game { bet: 10, pot: 0 }   // a field-accessible value (a Map)\ng.bet = 20                          // mutate fields in place\nlet b = g.bet\nreturn is_a(g, \"Game\")              // true — record literals carry _type"
  },
  {
   "n": 10,
   "title": "`soma serve` routes only the cell that owns `request`",
   "markdown": "```soma\n// other cells' signals are NOT auto-routed as HTTP endpoints\n```\n```soma\n// put every routable signal on the request-owning cell, delegating\n// to domain cells:\ncell Api {\n    face { signal request(...) -> String  signal place(...) -> Map }\n    on place(...) { return place_order(...) }   // delegate to Orders\n    on request(method, path, body) { ... }\n}\n```\n\n---",
   "wrong": "// other cells' signals are NOT auto-routed as HTTP endpoints",
   "right": "// put every routable signal on the request-owning cell, delegating\n// to domain cells:\ncell Api {\n    face { signal request(...) -> String  signal place(...) -> Map }\n    on place(...) { return place_order(...) }   // delegate to Orders\n    on request(method, path, body) { ... }\n}"
  },
  {
   "n": 11,
   "title": "Your handler shadows a builtin of the same name — when the argument count matches",
   "markdown": "`f(args)` calls the program's handler `f` if one takes that many arguments,\nthe builtin `f` otherwise. User code shadows the library, as everywhere else.\n\n```soma\non merge(a: Int, b: Int) { return a + b + 1000 }\non use_it()  { return merge(1, 2) }      // 1003 — your handler\non list()    { return list(1, 2) }       // [1, 2] — 2 ≠ 0 arguments: the builtin\n```\n`soma check` warns on the two confusing cases:\n```soma\non merge(a: Int, b: Int, c: Int) { … }\non use_it() { return merge(m1, m2) }\n// warning: call to 'merge' with 2 argument(s) … resolves to the BUILTIN merge():\n//          the handler G.merge takes [3]\non list() { let items = list() … }\n// warning: inside G.list, `list(…)` with 0 argument(s) calls the handler ITSELF\n//          (recursion), not the builtin list(). For an empty list write []\n```\nMethod calls (`xs.count(p)`) always go to builtins.",
   "wrong": "on merge(a: Int, b: Int) { return a + b + 1000 }\non use_it()  { return merge(1, 2) }      // 1003 — your handler\non list()    { return list(1, 2) }       // [1, 2] — 2 ≠ 0 arguments: the builtin",
   "right": "on merge(a: Int, b: Int, c: Int) { … }\non use_it() { return merge(m1, m2) }\n// warning: call to 'merge' with 2 argument(s) … resolves to the BUILTIN merge():\n//          the handler G.merge takes [3]\non list() { let items = list() … }\n// warning: inside G.list, `list(…)` with 0 argument(s) calls the handler ITSELF\n//          (recursion), not the builtin list(). For an empty list write []",
   "diagnostic": "warning: call to 'merge' with 2 argument(s) … resolves to the BUILTIN merge():"
  },
  {
   "n": 12,
   "title": "`assert_fails` needs an expression that RAISES, not a falsy bool",
   "markdown": "```soma\nassert_fails 1 == 2          // FAILS the test: 1==2 is just `false`, no error\n```\n```soma\nassert_fails xs[99]          // passes: out-of-bounds RAISES\nassert_fails transition(id, \"illegal\")   // passes: invalid transition raises\nassert !(1 == 2)             // for a falsy predicate, use plain assert + !\n```",
   "wrong": "assert_fails 1 == 2          // FAILS the test: 1==2 is just `false`, no error",
   "right": "assert_fails xs[99]          // passes: out-of-bounds RAISES\nassert_fails transition(id, \"illegal\")   // passes: invalid transition raises\nassert !(1 == 2)             // for a falsy predicate, use plain assert + !"
  },
  {
   "n": 13,
   "title": "Slot methods work on declared `memory` slots, not local maps",
   "markdown": "```soma\nlet seen = map()\nseen.set(\"k\", 1)             // error: `.set()` is a memory-slot method and 'seen' is a local — a local map is written with brackets: `seen[k] = v`\n```\n```soma\nlet seen = map()\nseen[\"k\"] = 1                // local maps use bracket indexing\nlet v = seen[\"k\"] ?? 0\n```\n`.get`/`.set`/`.has`/`.delete`/`.keys` are for `memory { slot: ... }` slots.",
   "wrong": "let seen = map()\nseen.set(\"k\", 1)             // error: `.set()` is a memory-slot method and 'seen' is a local — a local map is written with brackets: `seen[k] = v`",
   "right": "let seen = map()\nseen[\"k\"] = 1                // local maps use bracket indexing\nlet v = seen[\"k\"] ?? 0",
   "diagnostic": "error: `.set()` is a memory-slot method and 'seen' is a local — a local map is written with brackets: `seen[k] = v`"
  },
  {
   "n": 14,
   "title": "`on` is a reserved keyword",
   "markdown": "It can't be a parameter name or a map field read as `.on`. Use `enabled`,\n`active`, etc."
  },
  {
   "n": 15,
   "title": "No semicolons; statements are newline-separated",
   "markdown": "```soma\n{ a = 1; b = 2 }             // error: unexpected character ';' — Soma has no semicolons, one statement per line\n```\n```soma\n{\n    a = 1\n    b = 2\n}\n```",
   "wrong": "{ a = 1; b = 2 }             // error: unexpected character ';' — Soma has no semicolons, one statement per line",
   "right": "{\n    a = 1\n    b = 2\n}",
   "diagnostic": "error: unexpected character ';' — Soma has no semicolons, one statement per line"
  },
  {
   "n": 16,
   "title": "`given` is reserved (like `on`)",
   "markdown": "It's a face-declaration keyword — can't be a state name, param, or\nidentifier. `error: expected identifier, found Given`. Use `granted`, `input`, etc."
  },
  {
   "n": 17,
   "title": "What a test cell's `rules { }` accepts",
   "markdown": "`assert`, `assert_fails` (optionally `… matching \"text\"`), `let name = expr`\n(a fixture for the rules below), `mock think \"reply\"` / `mock think [\"a\", \"b\"]`\n/ `mock think error \"timeout\"`, and `property`. No bare statements: put logic\nin a handler and call it."
  },
  {
   "n": 18,
   "title": "One invariant, one slot",
   "markdown": "An invariant is checked per write, with only the written slot in scope.\n```soma\nmemory { a: Map<String, Int>  b: Map<String, Int>  invariant a + b <= 100 }\n// error: memory invariant references several slots (a, b) — ... Write\n//        one invariant per slot\n```\n`size` invariants are enforced on `delete` too: `invariant size >= 1`\nrejects removing the last entry.",
   "wrong": "memory { a: Map<String, Int>  b: Map<String, Int>  invariant a + b <= 100 }\n// error: memory invariant references several slots (a, b) — ... Write\n//        one invariant per slot",
   "diagnostic": "error: memory invariant references several slots (a, b) — ... Write"
  },
  {
   "n": 19,
   "title": "`7 / 2 = 3.5` everywhere — say `idiv` when you mean the integer quotient",
   "markdown": "`/` on two Ints is 3.5 (an Int only when exact) in the interpreter AND in\n`[native]` handlers. Native code is statically typed, so where the quotient\nmust be an Int it is checked instead of truncated:\n\n```soma\non mid(lo: Int, hi: Int) [native] {\n    let m = lo\n    m = (lo + hi) / 2        // m is an Int slot\n    return m\n}\n// mid(1, 2) → error: Int / Int is not exact here, and this spot can only\n//             hold an Int (7 / 2 is 3.5) — write idiv(a, b) ...\n```\n```soma\non mid(lo: Int, hi: Int) [native] { return idiv(lo + hi, 2) }   // 1, everywhere\n```\n`soma check` warns on Int / Int in native handlers; `soma fix f.cell --native-idiv`\nrewrites them (for code written when native `/` truncated). A `[native]`\ndivision by zero is an ordinary, `try`-catchable runtime error.",
   "wrong": "on mid(lo: Int, hi: Int) [native] {\n    let m = lo\n    m = (lo + hi) / 2        // m is an Int slot\n    return m\n}\n// mid(1, 2) → error: Int / Int is not exact here, and this spot can only\n//             hold an Int (7 / 2 is 3.5) — write idiv(a, b) ...",
   "right": "on mid(lo: Int, hi: Int) [native] { return idiv(lo + hi, 2) }   // 1, everywhere"
  },
  {
   "n": 20,
   "title": "A cost bound is only *proven* when every `think()` count is known",
   "markdown": "`think()` reached through a loop over a list, a lambda (`map(xs, x => think(..))`)\nor a recursive helper makes the bound unprovable — and an unprovable declared\nbound is a `soma check` **error** (the message reads `cost: 'tokens' bound is\nadvisory — …`, exit 1), not a note: a bound nobody can prove is a lie in the\nprogram's own words. Give the loop a literal `range(0, N)` or `[loop_bound(N)]`\nto get `bound proven` back, or remove the `cost` block. Calls to sibling\nhandlers are composed: `for i in range(0, 3) { helper() }` costs 3 × helper."
  },
  {
   "n": 21,
   "title": "Habits from Python / TypeScript that `soma check` now redirects",
   "markdown": "```soma\nif x == null { }          // error: 'null' does not exist — Soma's null is `()`; also `x ?? default`\nxs.includes(v)            // error: no method 'includes' — in Soma: contains(xs, v)\nrows.push(o)              // warning: result discarded — push returns a NEW list: rows = push(rows, o)\n[a[0]] + rest             // warning: `+` ADDS numeric lists element-wise — concat(a, b) / push(xs, x)\nlet t = lefft + 1         // error: undefined variable 'lefft' (did you mean 'left'?)\n```\nEveryday collection builtins: `contains(list|map|string, x)`, `slice(xs, start, end?)`\n(negative indexes count from the end), `keys(m)` / `values(m)` / `entries(m)`,\n`sort_by(rows, \"field\")` or `sort_by(rows, r => [0 - r.total, r.name], \"desc\"?)`\n(stable; a list key sorts on several keys), `round(x, digits)`.",
   "wrong": "if x == null { }          // error: 'null' does not exist — Soma's null is `()`; also `x ?? default`\nxs.includes(v)            // error: no method 'includes' — in Soma: contains(xs, v)\nrows.push(o)              // warning: result discarded — push returns a NEW list: rows = push(rows, o)\n[a[0]] + rest             // warning: `+` ADDS numeric lists element-wise — concat(a, b) / push(xs, x)\nlet t = lefft + 1         // error: undefined variable 'lefft' (did you mean 'left'?)",
   "diagnostic": "error: 'null' does not exist — Soma's null is `()`; also `x ?? default`"
  },
  {
   "n": 22,
   "title": "Transition guards see the calling handler's locals",
   "markdown": "```soma\nstate expense { initial: approved   approved -> paid { guard { amount < 10000 } } }\non pay(id: String) {\n    let amount = amounts.get(id) ?? 0      // the guard reads THIS `amount`\n    transition(id, \"paid\")                 // raises \"guard failed…\" when false\n}\n```\nA guard sees: the locals of the handler calling `transition()`, the cell's\nmemory slots, `_id`, `_from`, `_to`. `soma check` rejects a guard that reads a\nname its calling handler never binds. Guards are enforced at runtime; the\nmodel checker keeps the edge (an over-approximation, so safety results hold).",
   "wrong": "state expense { initial: approved   approved -> paid { guard { amount < 10000 } } }\non pay(id: String) {\n    let amount = amounts.get(id) ?? 0      // the guard reads THIS `amount`\n    transition(id, \"paid\")                 // raises \"guard failed…\" when false\n}"
  },
  {
   "n": 23,
   "title": "`soma.toml` is validated",
   "markdown": "A `soma.toml` that does not parse — or has an unknown key under `[verify]` — is\nan error for every command (it used to be ignored silently, so `[verify]`\nproperties were never checked). `[package]` is optional."
  },
  {
   "n": 24,
   "title": "Handlers are atomic; errors have kinds",
   "markdown": "A handler that raises leaves nothing behind: its writes and transitions are\nrolled back (a failing `try { }` block too, to where it began). Do not write\ncompensation code. Raise with `fail(\"kind\", \"detail\")` or\n`require cond else Tag`; catch with `let r = try { … }` and branch on `r.kind`\n(`\"not_found\"`, `\"invalid_transition\"`, `\"guard_failed\"`, `\"invariant\"`, …);\n`fail(r)` re-raises."
  },
  {
   "n": 25,
   "title": "`* -> failed` leaves EVERY state, final ones included",
   "markdown": "```soma\nstate s { initial: a   a -> paid   * -> failed }               // paid -> failed exists: paid is not final\nstate s { initial: a   a -> paid   * -> failed except [paid] }  // paid stays final\n```\n`soma verify` warns about the first form and prints the second.",
   "wrong": "state s { initial: a   a -> paid   * -> failed }               // paid -> failed exists: paid is not final\nstate s { initial: a   a -> paid   * -> failed except [paid] }  // paid stays final"
  },
  {
   "n": 26,
   "title": "A route in `request` and a handler of the same name",
   "markdown": "`soma serve` exposes every public handler at `/<name>/<args>`. With\n`on hold(id, qty)` and a route `\"/hold/\" + id`, the explicit route wins — but\nother `/hold/…` shapes still reach the handler. Prefix internal handlers with\n`_`. `soma check` warns."
  }
 ]
}