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

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.

Generated from soma describe on this file.

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

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

$ 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

Builtins used

fail, get_status, transition

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