Turnstile — a signal that may take any edge, and says so
`push` hands the visitor's action straight to transition(): the machine itself refuses what it has no edge for (a coin in an unlocked turnstile, a push on a locked one). The target is computed at run time, so no list of edges can describe the body — the frame says `moves * -> *`: this signal can take ANY edge of the machine. It is one visible line in the skeleton; `count` next to it shows the usual, narrow way.
A Soma program from the skeleton part of the verified corpus: 84 lines, cells Turnstile, TurnstileTests. It passes soma check with soma 3.2.4.
How it is built
The cell Turnstile declares 4 signals in its face: act(id: String, action: String) -> String, repair(id: String) -> String, count(id: String) -> Int and state_of(id: String) -> String; keeps 1 memory slot: passes (Map<String, Int>, persistent) under the invariant passes >= 0. Its state machine gate starts in locked and allows 4 transitions: locked -> unlocked, unlocked -> locked, * -> broken and broken -> locked. Handlers: act (line 39), repair (line 54), count (line 59) and state_of (line 63).
The file also holds 1 test cell (TurnstileTests) with 11 assertions, run by soma test.
The source
turnstile_any_edge.cell
// TURNSTILE — a signal that may take any edge, and says so.
//
// `push` hands the visitor's action straight to transition(): the machine
// itself refuses what it has no edge for (a coin in an unlocked turnstile,
// a push on a locked one). The target is computed at run time, so no list
// of edges can describe the body — the frame says `moves * -> *`: this
// signal can take ANY edge of the machine. It is one visible line in the
// skeleton; `count` next to it shows the usual, narrow way.
//
// soma check turnstile_any_edge.cell
// soma verify turnstile_any_edge.cell
// soma test turnstile_any_edge.cell
skeleton Turnstile {
memory {
passes: Map<String, Int> [persistent]
invariant passes >= 0
}
state gate {
initial: locked
locked -> unlocked // a coin
unlocked -> locked // a push: one person goes through
* -> broken
broken -> locked // the technician
}
face {
signal act(id: String, action: String) -> String
writes passes
moves * -> *
signal repair(id: String) -> String moves broken -> locked
signal count(id: String) -> Int reads passes
signal state_of(id: String) -> String reads gate
}
}
flesh Turnstile {
on act(id, action) {
let target = match action {
"coin" -> "unlocked"
"push" -> "locked"
"kick" -> "broken"
_ -> fail("UnknownAction", "'{action}' is not coin, push or kick")
}
let before = get_status(id) ?? "locked"
transition(id, target) // the machine decides
if before == "unlocked" && target == "locked" {
passes.set(id, (passes.get(id) ?? 0) + 1)
}
return target
}
on repair(id) {
transition(id, "broken", "locked")
return "locked"
}
on count(id) {
return passes.get(id) ?? 0
}
on state_of(id) {
return get_status(id) ?? "locked"
}
}
cell test TurnstileTests {
rules {
assert_fails act("g1", "push") // locked: no edge locked -> locked
assert act("g1", "coin") == "unlocked"
assert_fails act("g1", "coin") // already unlocked
assert act("g1", "push") == "locked"
assert count("g1") == 1
assert act("g1", "kick") == "broken"
assert_fails act("g1", "coin") // broken: only the technician
assert repair("g1") == "locked"
assert state_of("g1") == "locked"
assert_fails act("g1", "dance") matching "UnknownAction"
assert count("g1") == 1
}
}What the toolchain says
$ soma check turnstile_any_edge.cell
✓ All checks passed.$ soma test turnstile_any_edge.cell
test TurnstileTests ...
✓ assert_fails act("g1", "push") — raised invalid transition: locked → locked. Current state: 'locked'. Valid targets: [unlocked, broken]
✓ assert act("g1", "coin") == "unlocked"
✓ assert_fails act("g1", "coin") — raised invalid transition: unlocked → unlocked. Current state: 'unlocked'. Valid targets: [locked, broken]
✓ assert act("g1", "push") == "locked"
✓ assert count("g1") == 1
✓ assert act("g1", "kick") == "broken"
✓ assert_fails act("g1", "coin") — raised invalid transition: broken → unlocked. Current state: 'broken'. Valid targets: [locked]
✓ assert repair("g1") == "locked"
✓ assert state_of("g1") == "locked"
✓ assert_fails act("g1", "dance") matching "UnknownAction" — raised UnknownAction: 'dance' is not coin, push or kick
✓ assert count("g1") == 1
11 tests: 11 passed, 0 failed$ soma verify turnstile_any_edge.cell
Verifying turnstile_any_edge.cell...
State machine 'gate': 3 states, initial 'locked'
States: [broken, locked, unlocked]
✓ 3 states, 5 transitions
✓ all states reachable from 'locked'
✓ reactive machine: no terminal state, and every reachable state can return to 'locked'
✓ no deadlocks
✓ liveness: every reachable state can return to 'locked' (reactive machine)
✓ wildcard transitions: * -> [broken]
⚠ dynamic transition targets: handler `act` uses a dynamic (computed) transition target — the state these handlers move to is computed at run time (from input or an LLM answer), so the machine's safety is checked at run time for them, not proven
✓ termination: all 4 handlers structurally terminate
⚠ refinement (partial — dynamic targets fall back to runtime check): handler `act` calls transition() with a computed target — which edge it takes is checked at run time (invalid_transition), not proven; write the target as a literal state to have it verified
✓ refinement (partial — dynamic targets fall back to runtime check): handler `act` ⟶ <dynamic>
✓ refinement (partial — dynamic targets fall back to runtime check): handler `repair` ⟶ {broken → locked}
Turnstile/invariants:
✓ invariant passes >= 0 — writer 'act' proven by induction (writes (passes.get(id) ?? 0) + 1)
10 passed, 2 warnings, 0 failures
Temporal properties for 'gate':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK