soma.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.

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.

Generated from soma describe on this file.

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

Run on a fresh copy of the program at build time; the outputs are pasted unedited.

$ 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 OK

Builtins used

fail, from_json, get_status, len, map, response, transition, with

Source file: /corpus/services/warehouse_reservations.cell · examples/corpus/services/warehouse_reservations.cell