soma.cell

Order lifecycle as a typed state machine

Demonstrates: a sum-typed `state` machine (states ARE variants, so a typo in transition() is a compile error), guarded transitions, and assert_fails on illegal moves. `soma verify` proves the machine always reaches a terminal (delivered or cancelled).

A Soma program from the state_machines part of the verified corpus: 58 lines, cells OrderStatus, OrderFlow, OrderFlowTests. It passes soma check with soma 3.2.4.

How it is built

The cell OrderFlow declares 7 signals in its face: open(id: String) -> String, place(id: String) -> String, pay(id: String) -> String, ship(id: String) -> String, deliver(id: String) -> String, cancel(id: String) -> String and locate(id: String) -> String. Its state machine order starts in Draft and allows 5 transitions: Draft -> Placed, Placed -> Paid, Paid -> Shipped, Shipped -> Delivered and * -> Cancelled. Cancelled and Delivered are terminal. Handlers: open (line 32), place (line 33), pay (line 34), ship (line 35), deliver (line 36), cancel (line 37) and locate (line 38).

The file also holds 1 test cell (OrderFlowTests) with 8 assertions, run by soma test.

Generated from soma describe on this file.

The source

order_lifecycle.cell

// Order lifecycle as a typed state machine.
// Demonstrates: a sum-typed `state` machine (states ARE variants, so a
// typo in transition() is a compile error), guarded transitions, and
// assert_fails on illegal moves. `soma verify` proves the machine always
// reaches a terminal (delivered or cancelled).

cell type OrderStatus {
    variants { Draft  Placed  Paid  Shipped  Delivered  Cancelled }
}

cell OrderFlow {
    face {
        signal open(id: String) -> String
        signal place(id: String) -> String
        signal pay(id: String) -> String
        signal ship(id: String) -> String
        signal deliver(id: String) -> String
        signal cancel(id: String) -> String
        signal locate(id: String) -> String
        promise "every order eventually reaches Delivered or Cancelled"
    }

    state order: OrderStatus {
        initial: Draft
        Draft   -> Placed
        Placed  -> Paid
        Paid    -> Shipped
        Shipped -> Delivered
        *       -> Cancelled
    }

    on open(id: String)    { return get_status(id) }
    on place(id: String)   { transition(id, Placed)    return get_status(id) }
    on pay(id: String)     { transition(id, Paid)      return get_status(id) }
    on ship(id: String)    { transition(id, Shipped)   return get_status(id) }
    on deliver(id: String) { transition(id, Delivered) return get_status(id) }
    on cancel(id: String)  { transition(id, Cancelled) return get_status(id) }
    on locate(id: String)  { return get_status(id) }
}

cell test OrderFlowTests {
    rules {
        // happy path: Draft -> Placed -> Paid -> Shipped -> Delivered
        assert open("a") == "Draft"
        assert place("a") == "Placed"
        assert pay("a") == "Paid"
        assert ship("a") == "Shipped"
        assert deliver("a") == "Delivered"

        // a fresh order can be cancelled from any state (the * arrow)
        assert cancel("b") == "Cancelled"

        // illegal skips are rejected by the machine
        assert_fails ship("c")      // c is still Draft, cannot jump to Shipped
        assert_fails deliver("d")   // d is still Draft, cannot jump to Delivered
    }
}

What the toolchain says

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

$ soma check order_lifecycle.cell
note: promise on 'OrderFlow' is documentation (not machine-verifiable): "every order eventually reaches Delivered or Cancelled"
  --> order_lifecycle.cell:20:17
   |
20 |         promise "every order eventually reaches Delivered or Cancelled"
   |                 ^
✓ All checks passed (1 note above).
$ soma test order_lifecycle.cell
test OrderFlowTests ...
  ✓ assert open("a") == "Draft"
  ✓ assert place("a") == "Placed"
  ✓ assert pay("a") == "Paid"
  ✓ assert ship("a") == "Shipped"
  ✓ assert deliver("a") == "Delivered"
  ✓ assert cancel("b") == "Cancelled"
  ✓ assert_fails ship("c") — raised invalid transition: Draft → Shipped. Current state: 'Draft'. Valid targets: [Placed, Cancelled]
  ✓ assert_fails deliver("d") — raised invalid transition: Draft → Delivered. Current state: 'Draft'. Valid targets: [Placed, Cancelled]

8 tests: 8 passed, 0 failed
$ soma verify order_lifecycle.cell
Verifying order_lifecycle.cell...

State machine 'order': 6 states, initial 'Draft'
  States: [Cancelled, Delivered, Draft, Paid, Placed, Shipped]

  ✓ 6 states, 9 transitions
  ✓ all states reachable from 'Draft'
  ✓ terminal states: [Cancelled]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ wildcard transitions: * -> [Cancelled]
  ⚠ `* -> Cancelled` also adds Delivered -> Cancelled — so Delivered is not final. If it should stay final, write `* -> Cancelled except [Delivered]`
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (5 handlers, 5 literal transitions, 0 dynamic)
  ✓ termination: all 7 handlers structurally terminate
  ✓ refinement: handler `place` ⟶ {Placed}
  ✓ refinement: handler `pay` ⟶ {Paid}
  ✓ refinement: handler `ship` ⟶ {Shipped}
  ✓ refinement: handler `deliver` ⟶ {Delivered}
  ✓ refinement: handler `cancel` ⟶ {Cancelled}
  · transition sources: `place`, `pay`, `ship`, `deliver`, `cancel` 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)

13 passed, 1 warnings, 0 failures

  Temporal properties for 'order':
  ✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK

Builtins used

get_status, transition

Source file: /corpus/state_machines/order_lifecycle.cell · examples/corpus/state_machines/order_lifecycle.cell