Soma 2.8.0 — 2026-09-20
-
Invariants BETWEEN slots of a cell: invariant (reserved ?? 0) <= (stock ?? 0) is checked on every write to either slot (the written one is its new value, the others are read at the same key); verify reports it runtime-checked. The bare form is refused with the fix in the message.
-
refusal(kind, detail?): the body and HTTP status a raised error of that kind would give — refuse a request AND keep what the handler recorded (raising rolls it back).
-
soma verify names the cross-cell rules it does not prove (note: cross-cell: handler X transitions its machine and acts on cell Y).
-
Check: an ensure in a [task] handler after writes and a think(); a face parameter whose name differs from the handler's.
-
delete on an [immutable] List refuses whatever the index (out of range answered false); dropping a stalled SSE subscriber is logged, and the docs give its real memory cost.
-
Storage: a write the database refuses (a read-only .soma_data, a full disk) raises kind storage and rolls the handler back — set / push were silent no-ops answered 200; rows[i] on a persistent List is an indexed read again (20 000 reads: 0.09 s, was 0.72 s; 100 000 took 17 s).
-
Packages: soma install reinstalls a path dependency whose source changed (every build and test kept running against the first copy).
-
soma test <dir> / soma check <dir> run every .cell under a directory.
-
soma serve: SIGINT stops it (a shell that starts it in the background leaves SIGINT ignored); -w checks the new file before stopping the running one, and says when the server died; -p 0 prints the port the OS gave; a damaged database is refused before the banner.
-
CLI: a Map argument carrying _type / _variant / _values at any depth is refused, as over HTTP.
-
The prover reaches further: x % n / mod(x, n), x * x, len("abc"), idiv, sqrt_int, band(x, mask), and the documented monotone counter invariant value >= (seq.get(key) ?? 0) written as + n or max(…).
-
Soundness: vote() is in the call graph of every proof — a size invariant past a vote whose target wrote the slot was "proven" (and raised), recursion through vote was "proven" to terminate, its latency counted one voter instead of k; horde() targets and callbacks are in the termination graph (a horde restarted from on_done ran 20 821 times under "✓ terminates").
-
The wait for a rate limit (SOMA_LLM_RPM, [agent] rpm) and a mock's latency are inside the think's timeout (a "proven" 2 s took 60 s).
-
Bus: an event emitted while a [peers] peer's link is down is logged NOT delivered to it, even while other links are up (50 alerts lost, 3 logged); docs: emit reaches every link, receivers filter by [bus] accept.
-
"{Other.ask(1)}" is fine again; range(a, b, step) has a known length for cost bounds; docs: what usd counts.
-
Budgets: set_budget(0) means no more model calls (it meant "no limit": a computed quota - spent reaching 0 lifted the cap); a negative budget is refused; vote()'s concurrent voters reserve against what set_budget left and their tokens and trace count for the caller (25 voters spent 1 550 tokens under set_budget(50), uncounted); a horde without budget_tokens counts every think of it (callbacks, votes, nested hordes) in horde_status.tokens; check warns on a computed budget_tokens.
-
http_get / http_post never turn a remote JSON with _type / _variant into one of your records (a reflected Admin matched the Admin arm): such a body stays text.
-
"{len}", "{helper}", "{Cell}" are check errors (a function or cell is not a value); "{true}" / "{false}" interpolate.
-
[task] lints: a read in an if around the think, through a helper, or a write through a helper is stale; a value re-read after the think is not; transition() / emit before a think in a try count as writes; a literal delegate to a thinking handler is a think.
-
Termination: map("k", v) in a lambda is not a function call, and a parameter typed as data is not a function value (verify --strict failed on CSV-row building).
-
An Int given for a Float field or parameter becomes a Float.
-
Soundness: a [task] tick (every … [task]) and vote() end a step — the prover keeps no fact across them (a "proven" members.size <= 3 was broken at run time); the stale-read, try-write and no-think lints follow helpers, other cells' agents, emit listeners and "{think(…)}", and apply to [task] ticks.
-
Hordes: a horde whose task starts a horde of itself is flagged by verify and stopped (hordes started from tasks nest at most 4 deep; rounds started by on_done do not nest; at most 1 000 000 queued tasks per process); a server that lost a horde's lease stops recording its tasks (results after on_done); a horde's creator no longer resumes it a second time (double concurrency); on_error counts in the cost bound; seed covers apply and on_done; instance memory is per owner cell; status says done inside on_done; check: seed / instance / on_done parameter types.
-
Records: r.field += 1, r["field"], xs[0].field = v, with(record, …); a field write must match its declared type.
-
Strings: "{\"a\": {\"b\": 1}}" kept its last } — a } closing a literal { is never half of a }} escape.
-
A handler another handler called can only lower the caller's set_budget.
-
Docs: SOMA_LLM_MOCK=rules:<file> format; keys(record).