Warehouse reservations — two cells: a stock ledger with hard bounds and a reservation lifecycle with a supervisor guard, behind one HTTP router
Proven (`soma verify --strict` is green): on-hand quantity stays in [0, 10000] per SKU and there are at most 500 SKUs — every writer narrows its value with a `require` before the one write, so the invariants hold by induction instead of being checked at run time; shipped requires picked AND held; released never ships; rejected and shipped are final (`* -> rejected except [shipped, released]`). Enforced at the transition: a reservation above 100 units only reaches `held` when a supervisor is named (a guard). A hold that would overdraw stock ends `rejected` and stock is untouched — the failing `try` block is rolled back to where it started.
A Soma program from the services part of the verified corpus: 187 lines, cells Stock, Reservations, WarehouseTests. It passes soma check with soma 3.2.4.
How it is built
The cell Stock declares 5 signals in its face: receive(sku: String, qty: Int) -> Int, on_hand(sku: String) -> Int, _take(sku: String, qty: Int) -> Int, _give_back(sku: String, qty: Int) -> Int and _add_sku(sku: String, qty: Int) -> Int; keeps 1 memory slot: units (Map<String, Int>, persistent) under the invariant units >= 0 && units <= 10000 and units.size <= 500. Handlers: on_hand (line 32) and receive (line 35). Private helpers: _add_sku, _take and _give_back.
The cell Reservations declares 7 signals in its face: reserve(id: String, sku: String, qty: Int) -> Map, hold(id: String, supervisor: String) -> Map, pick(id: String) -> Map, ship(id: String) -> Map, release(id: String) -> Map, reservation(id: String) -> Map and request(method: String, path: String, body: String) -> Map; keeps 2 memory slots: skus (Map<String, String>, persistent) and qtys (Map<String, Int>, persistent) under the invariant qtys > 0. Its state machine lifecycle starts in requested and allows 5 transitions: requested -> held (guarded), held -> picked, picked -> shipped, held -> released and * -> rejected. rejected, released and shipped are terminal. Handlers: reserve (line 87), hold (line 99), pick (line 111), ship (line 112), release (line 113), reservation (line 118) and request (line 127). Private helpers: _qty and _route.
The file also holds 1 test cell (WarehouseTests) with 27 assertions, run by soma test.
The source
soma.toml
[package]
name = "services-corpus"
version = "0.1.0"
description = "Whole verified services: HTTP + lifecycle + invariants, in one file each"
[agent]
mock = "echo"
# payments_approval.cell — the properties its README promises
[verify]
cells = ["Payments"]
eventually = ["paid", "rejected"]
[verify.before.paid]
requires = ["authorized"]
[verify.after.rejected]
never = ["paid", "authorized", "signed_off"]
[verify.after.paid]
never = ["rejected"]warehouse_reservations.cell
// WAREHOUSE RESERVATIONS — two cells: a stock ledger with hard bounds and a
// reservation lifecycle with a supervisor guard, behind one HTTP router.
//
// Proven (`soma verify --strict` is green): on-hand quantity stays in
// [0, 10000] per SKU and there are at most 500 SKUs — every writer narrows
// its value with a `require` before the one write, so the invariants hold by
// induction instead of being checked at run time; shipped requires picked AND
// held; released never ships; rejected and shipped are final
// (`* -> rejected except [shipped, released]`).
// Enforced at the transition: a reservation above 100 units only reaches
// `held` when a supervisor is named (a guard). A hold that would overdraw
// stock ends `rejected` and stock is untouched — the failing `try` block is
// rolled back to where it started.
//
// soma check warehouse_reservations.cell
// soma verify --strict warehouse_reservations.cell
// soma test warehouse_reservations.cell
cell Stock {
face {
signal receive(sku: String, qty: Int) -> Int
signal on_hand(sku: String) -> Int
signal _take(sku: String, qty: Int) -> Int
signal _give_back(sku: String, qty: Int) -> Int
signal _add_sku(sku: String, qty: Int) -> Int
}
memory {
units: Map<String, Int> [persistent]
invariant units >= 0 && units <= 10000
invariant units.size <= 500
}
on on_hand(sku: String) { return units.get(sku) ?? 0 }
// a NEW SKU is the only write that grows the slot: the require on its
// size makes `units.size <= 500` provable
on receive(sku: String, qty: Int) {
let have = units.get(sku) ?? 0
require qty > 0 && have + qty <= 10000 else InvalidQuantity "{qty} of {sku}: {have} on hand, cap 10000"
if units.get(sku) == () { _add_sku(sku, have + qty) } else { units.set(sku, have + qty) }
return on_hand(sku)
}
on _add_sku(sku: String, qty: Int) {
require len(units) < 500 else SkuLimit "at most 500 SKUs"
require qty >= 0 && qty <= 10000 else InvalidQuantity
units.set(sku, qty)
return qty
}
on _take(sku: String, qty: Int) {
let have = units.get(sku) ?? 0
require units.get(sku) != () else UnknownSku "{sku} was never received"
require qty > 0 && qty <= have else Insufficient "{qty} of {sku} requested, {have} on hand"
units.set(sku, have - qty)
return have - qty
}
on _give_back(sku: String, qty: Int) {
let have = units.get(sku) ?? 0
require units.get(sku) != () else UnknownSku "{sku} was never received"
require qty > 0 && have + qty <= 10000 else InvalidQuantity
units.set(sku, have + qty)
return have + qty
}
}
cell Reservations {
face {
signal reserve(id: String, sku: String, qty: Int) -> Map
signal hold(id: String, supervisor: String) -> Map
signal pick(id: String) -> Map
signal ship(id: String) -> Map
signal release(id: String) -> Map
signal reservation(id: String) -> Map
signal request(method: String, path: String, body: String) -> Map
}
memory {
skus: Map<String, String> [persistent]
qtys: Map<String, Int> [persistent]
invariant qtys > 0
}
state lifecycle {
initial: requested
requested -> held { guard { qty <= 100 || supervisor != "" } }
held -> picked
picked -> shipped
held -> released
* -> rejected except [shipped, released]
}
on reserve(id: String, sku: String, qty: Int) {
if skus.get(id) != () { fail("conflict", "reservation {id} exists") }
require qty > 0 else InvalidQuantity "qty must be positive, got {qty}"
skus.set(id, sku)
qtys.set(id, qty)
return reservation(id)
}
// Take the stock, then move: if the stock ledger or the guard refuses,
// the `try` block is rolled back (nothing was taken) and the reservation
// is rejected. The rejection is RETURNED, not raised: raising would roll
// the whole handler back, rejection included — an error means "nothing
// happened".
on hold(id: String, supervisor: String) {
let qty = _qty(id)
let r = try {
_take(skus.get(id), qty)
transition(id, "held") // the guard reads `qty` and `supervisor`
}
if r.error != () {
transition(id, "rejected")
return with(reservation(id), "reason", r.detail)
}
return reservation(id)
}
on pick(id: String) { transition(id, "picked") return reservation(id) }
on ship(id: String) { transition(id, "shipped") return reservation(id) }
on release(id: String) {
transition(id, "released") // only from held: picked -> released does not exist
_give_back(skus.get(id), _qty(id))
return reservation(id)
}
on reservation(id: String) {
return map("id", id, "sku", skus.get(id), "qty", _qty(id), "state", get_status(id))
}
on _qty(id: String) {
let q = qtys.get(id)
if q == () { fail("not_found", "reservation {id}") }
return q
}
on request(method: String, path: String, body: String) {
let r = try { _route(method, path, body) }
if r.error == () { return r.value }
if r.kind == "not_found" { return response(404, map("error", r.detail)) }
if r.kind == "conflict" || r.kind == "invalid_transition" { return response(409, map("error", r.error)) }
return response(422, map("error", r.error))
}
on _route(method: String, path: String, body: String) {
return match map("method", method, "path", path) {
{method: "POST", path: "/reservations"} -> {
let b = from_json(body)
reserve(b.id, b.sku, b.qty)
}
{method: "POST", path: "/reservations/hold/" + id} -> hold(id, (from_json(body) ?? map()).supervisor ?? "")
{method: "POST", path: "/reservations/pick/" + id} -> pick(id)
{method: "POST", path: "/reservations/ship/" + id} -> ship(id)
{method: "POST", path: "/reservations/release/" + id} -> release(id)
{method: "GET", path: "/reservations/" + id} -> reservation(id)
{method: "GET", path: "/stock/" + sku} -> map("sku", sku, "on_hand", on_hand(sku))
_ -> response(404, map("error", "no route"))
}
}
}
cell test WarehouseTests {
rules {
assert receive("widget", 50) == 50
// happy path
assert reserve("r1", "widget", 20).state == "requested"
assert hold("r1", "").state == "held"
assert on_hand("widget") == 30
assert pick("r1").state == "picked"
assert_fails release("r1") matching "invalid transition" // picked -> released does not exist
assert ship("r1").state == "shipped"
assert_fails hold("r1", "") matching "invalid transition" // shipped is final
// overdraw: rejected, and the stock is untouched
assert reserve("r2", "widget", 100).state == "requested"
assert hold("r2", "").state == "rejected"
assert reservation("r2").state == "rejected"
assert on_hand("widget") == 30
// supervisor guard both ways
assert receive("bolt", 1000) == 1000
assert reserve("r3", "bolt", 500).state == "requested"
assert hold("r3", "").state == "rejected" // > 100 units, nobody signed
assert reservation("r3").state == "rejected"
assert reserve("r4", "bolt", 500).state == "requested"
assert hold("r4", "ana").state == "held"
assert release("r4").state == "held" || reservation("r4").state == "released"
assert on_hand("bolt") == 1000
// bounds
assert_fails receive("widget", 20000) matching "InvalidQuantity"
assert_fails reserve("r5", "widget", 0) matching "InvalidQuantity"
assert_fails units.set("widget", 20000) matching "invariant" // the wall itself
assert_fails _take("ghost", 1) matching "UnknownSku"
// HTTP
assert request("GET", "/reservations/nope", "")._status == 404
assert request("POST", "/reservations/ship/r2", "")._status == 409 // rejected is final
assert request("GET", "/stock/bolt", "").on_hand == 1000
}
}What the toolchain says
$ soma check warehouse_reservations.cell
✓ All checks passed.$ soma test warehouse_reservations.cell
test WarehouseTests ...
✓ assert receive("widget", 50) == 50
✓ assert reserve("r1", "widget", 20).state == "requested"
✓ assert hold("r1", "").state == "held"
✓ assert on_hand("widget") == 30
✓ assert pick("r1").state == "picked"
✓ assert_fails release("r1") matching "invalid transition" — raised invalid transition: picked → released. Current state: 'picked'. Valid targets: [shipped, rejected]
✓ assert ship("r1").state == "shipped"
✓ assert_fails hold("r1", "") matching "invalid transition" — raised invalid transition: shipped → rejected. Current state: 'shipped'. Valid targets: []
✓ assert reserve("r2", "widget", 100).state == "requested"
✓ assert hold("r2", "").state == "rejected"
✓ assert reservation("r2").state == "rejected"
✓ assert on_hand("widget") == 30
✓ assert receive("bolt", 1000) == 1000
✓ assert reserve("r3", "bolt", 500).state == "requested"
✓ assert hold("r3", "").state == "rejected"
✓ assert reservation("r3").state == "rejected"
✓ assert reserve("r4", "bolt", 500).state == "requested"
✓ assert hold("r4", "ana").state == "held"
✓ assert release("r4").state == "held" || reservation("r4").state == "released"
✓ assert on_hand("bolt") == 1000
✓ assert_fails receive("widget", 20000) matching "InvalidQuantity" — raised InvalidQuantity: 20000 of widget: 30 on hand, cap 10000
✓ assert_fails reserve("r5", "widget", 0) matching "InvalidQuantity" — raised InvalidQuantity: qty must be positive, got 0
✓ assert_fails units.set("widget", 20000) matching "invariant" — raised memory invariant violated on 'units': units >= 0 && units <= 10000 — rejected write of 20000 (key "widget"); the slot is unchanged
✓ assert_fails _take("ghost", 1) matching "UnknownSku" — raised UnknownSku: ghost was never received
✓ assert request("GET", "/reservations/nope", "")._status == 404
✓ assert request("POST", "/reservations/ship/r2", "")._status == 409
✓ assert request("GET", "/stock/bolt", "").on_hand == 1000
27 tests: 27 passed, 0 failed$ soma verify warehouse_reservations.cell
Verifying warehouse_reservations.cell...
note: soma.toml [verify] cells names 'Payments', which this file does not define — its properties are not checked here
State machine 'lifecycle': 6 states, initial 'requested'
States: [held, picked, rejected, released, requested, shipped]
✓ 6 states, 7 transitions
✓ all states reachable from 'requested'
✓ terminal states: [shipped, released, rejected]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
· guarded edges kept in the model (liveness and `eventually` above assume they can pass): requested -> held
✓ wildcard transitions: * -> [rejected]
✓ think-isolated: CTL safety properties hold regardless of LLM output (4 handlers, 5 literal transitions, 0 dynamic)
✓ termination: all 9 handlers structurally terminate
✓ refinement: handler `hold` ⟶ {held [(match arm _)], rejected [if r.error != ()]}
✓ refinement: handler `pick` ⟶ {picked}
✓ refinement: handler `ship` ⟶ {shipped}
✓ refinement: handler `release` ⟶ {released}
· transition sources: `hold`, `pick`, `ship`, `release` call transition(id, target) without declaring the source — the runtime refuses a move the graph forbids, but the proof does not see where the handler starts; `transition(id, from, to)` names the edge (checked by `soma check`, refused at run time from any other state)
⚠ guard `qty <= 100 || supervisor != ""` on requested -> held: runtime-checked in `hold` — put `require qty <= 100 || supervisor != "" else Tag` before the transition() (no require or if establishes qty, supervisor there)
Stock/invariants:
✓ invariant units >= 0 && units <= 10000 — writer '_add_sku' proven by induction (writes qty)
✓ invariant units >= 0 && units <= 10000 — writer '_give_back' proven by induction (writes have + qty)
✓ invariant units >= 0 && units <= 10000 — writer '_take' proven by induction (writes have - qty)
✓ invariant units >= 0 && units <= 10000 — writer 'receive' proven by induction (writes have + qty)
✓ invariant units.size <= 500 — writer '_add_sku' proven by induction (writes qty)
✓ invariant units.size <= 500 — writer '_give_back' proven by induction (writes have + qty)
✓ invariant units.size <= 500 — writer '_take' proven by induction (writes have - qty)
✓ invariant units.size <= 500 — writer 'receive' proven by induction (writes have + qty)
Reservations/invariants:
✓ invariant qtys > 0 — writer 'reserve' proven by induction (writes qty)
21 passed, 1 warnings, 0 failures
Temporal properties for 'lifecycle':
✓ deadlock_free — no deadlocks in any reachable state
soma.toml: 6 user-defined properties loaded — NONE applies to this file ([verify] cells = ["Payments"] names no cell of it)
Temporal: 1 passed, 0 failed
note: state machines are verified per cell. A value crosses a cell boundary by a face contract (`requires` / `ensures` on the signal); a rule between two cells' machines is proven when soma.toml states it — `[verify.before."Pay.paid"] requires = ["Approval.signed_off"]` explores the product of the machines.
VERIFY OKBuiltins used
fail, from_json, get_status, len, map, response, transition, with