soma.cell

Ticket desk — one cell written skeleton-first

The skeleton is the whole design: memory, the lifecycle, and for each signal its frame (what its body may read, write, call, which edges it may take). The flesh below holds the bodies; `soma check` refuses a body that does anything its frame does not say. Read the skeleton and you know that only `open` creates a ticket, that `close` cannot reopen one, and that `summary` writes nothing — without reading a body.

A Soma program from the skeleton part of the verified corpus: 122 lines, cells Desk, DeskTests. It passes soma check with soma 3.2.4.

How it is built

The cell Desk declares 7 signals in its face: open(id: String, title: String) -> Map, assign(id: String, owner: String) -> String, resolve(id: String, by: String) -> String, reopen(id: String) -> String, close(id: String) -> String, summary(id: String) -> Map and count() -> Int; keeps 3 memory slots: titles (Map<String, String>, persistent), owners (Map<String, String>, persistent) and opened (Map<String, Int>, persistent) under the invariant opened >= 0. Its state machine ticket starts in new and allows 5 transitions: new -> assigned, assigned -> resolved, resolved -> closed, resolved -> assigned and new -> closed. closed is terminal. Handlers: open (line 53), assign (line 60), resolve (line 67), reopen (line 74), close (line 79), summary (line 85) and count (line 90). Private helpers: _must_exist.

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

Generated from soma describe on this file.

The source

ticket_desk.cell

// TICKET DESK — one cell written skeleton-first.
//
// The skeleton is the whole design: memory, the lifecycle, and for each
// signal its frame (what its body may read, write, call, which edges it
// may take). The flesh below holds the bodies; `soma check` refuses a body
// that does anything its frame does not say. Read the skeleton and you
// know that only `open` creates a ticket, that `close` cannot reopen one,
// and that `summary` writes nothing — without reading a body.
//
//   soma check    ticket_desk.cell
//   soma skeleton ticket_desk.cell Desk.assign      the brief for one body
//   soma verify   ticket_desk.cell
//   soma test     ticket_desk.cell

skeleton Desk {
    memory {
        titles: Map<String, String> [persistent]
        owners: Map<String, String> [persistent]
        opened: Map<String, Int> [persistent]         // how many tickets, under "n"
        invariant opened >= 0
    }

    state ticket {
        initial: new
        new -> assigned
        assigned -> resolved
        resolved -> closed
        resolved -> assigned        // the fix did not hold: back to its owner
        new -> closed               // not a real ticket
    }

    face {
        signal open(id: String, title: String) -> Map
            writes titles, opened
            calls count
        signal assign(id: String, owner: String) -> String
            reads titles
            writes owners
            moves new -> assigned
        signal resolve(id: String, by: String) -> String
            reads titles, owners
            moves assigned -> resolved
        signal reopen(id: String) -> String             moves resolved -> assigned
        signal close(id: String) -> String
            reads titles
            moves resolved -> closed, new -> closed
        signal summary(id: String) -> Map               reads titles, owners, ticket
        signal count() -> Int                           reads opened
    }
}

flesh Desk {
    on open(id, title) {
        require titles.get(id) == () else Taken "ticket '{id}' exists"
        titles.set(id, title)
        opened.set("n", count() + 1)
        return map("id", id, "title", title, "state", "new")
    }

    on assign(id, owner) {
        _must_exist(id)
        transition(id, "new", "assigned")
        owners.set(id, owner)
        return owner
    }

    on resolve(id, by) {
        _must_exist(id)
        require owners.get(id) == by else Forbidden "only its owner resolves a ticket"
        transition(id, "assigned", "resolved")
        return "resolved"
    }

    on reopen(id) {
        transition(id, "resolved", "assigned")
        return "assigned"
    }

    on close(id) {
        _must_exist(id)
        transition(id, "closed")
        return "closed"
    }

    on summary(id) {
        _must_exist(id)
        return map("id", id, "title", titles.get(id), "owner", owners.get(id) ?? "nobody", "state", get_status(id) ?? "new")
    }

    on count() {
        return opened.get("n") ?? 0
    }

    // a private helper: `_name`, typed. What it reads counts against every
    // signal that calls it: each of them has `titles` in its frame — leave
    // it out of one and `soma check` names that signal and the clause.
    on _must_exist(id: String) {
        require titles.get(id) != () else NotFound "no ticket '{id}'"
    }
}

cell test DeskTests {
    rules {
        assert open("t1", "printer on fire").state == "new"
        assert count() == 1
        assert_fails open("t1", "again") matching "Taken"
        assert assign("t1", "ines") == "ines"
        assert_fails resolve("t1", "omar") matching "Forbidden"
        assert resolve("t1", "ines") == "resolved"
        assert reopen("t1") == "assigned"
        assert resolve("t1", "ines") == "resolved"
        assert close("t1") == "closed"
        assert_fails reopen("t1")                   // closed is terminal
        assert summary("t1").owner == "ines"
        assert_fails summary("nope") matching "NotFound"

        assert open("t2", "spam").state == "new"
        assert close("t2") == "closed"              // straight from new
        assert count() == 2
    }
}

What the toolchain says

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

$ soma check ticket_desk.cell
✓ All checks passed.
$ soma test ticket_desk.cell
test DeskTests ...
  ✓ assert open("t1", "printer on fire").state == "new"
  ✓ assert count() == 1
  ✓ assert_fails open("t1", "again") matching "Taken" — raised Taken: ticket 't1' exists
  ✓ assert assign("t1", "ines") == "ines"
  ✓ assert_fails resolve("t1", "omar") matching "Forbidden" — raised Forbidden: only its owner resolves a ticket
  ✓ assert resolve("t1", "ines") == "resolved"
  ✓ assert reopen("t1") == "assigned"
  ✓ assert resolve("t1", "ines") == "resolved"
  ✓ assert close("t1") == "closed"
  ✓ assert_fails reopen("t1") — raised invalid transition: closed → assigned — transition(id, "resolved", "assigned") declares its source, and 't1' is in 'closed', not 'resolved'
  ✓ assert summary("t1").owner == "ines"
  ✓ assert_fails summary("nope") matching "NotFound" — raised NotFound: no ticket 'nope'
  ✓ assert open("t2", "spam").state == "new"
  ✓ assert close("t2") == "closed"
  ✓ assert count() == 2

15 tests: 15 passed, 0 failed
$ soma verify ticket_desk.cell
Verifying ticket_desk.cell...

State machine 'ticket': 4 states, initial 'new'
  States: [assigned, closed, new, resolved]

  ✓ 4 states, 5 transitions
  ✓ all states reachable from 'new'
  ✓ terminal states: [closed]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (4 handlers, 4 literal transitions, 0 dynamic)
  ✓ termination: all 8 handlers structurally terminate
  ✓ refinement: handler `assign` ⟶ {new → assigned}
  ✓ refinement: handler `resolve` ⟶ {assigned → resolved}
  ✓ refinement: handler `reopen` ⟶ {resolved → assigned}
  ✓ refinement: handler `close` ⟶ {closed}
  · transition sources: `close` calls 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)

Desk/invariants:

  ✓ invariant opened >= 0 — writer 'open' proven by induction (writes count() + 1)

12 passed, 0 warnings, 0 failures

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

Builtins used

get_status, map, transition

Source file: /corpus/skeleton/ticket_desk.cell · examples/corpus/skeleton/ticket_desk.cell