soma.cell

A two-road traffic-light interlock proven safe by construction

The single machine cycles ns_go -> ns_caution -> ew_go -> ew_caution and back, with an all_red emergency reachable from anywhere. Because no state ever has both roads green, the model checker proves the safety property `never = []` over a machine that can never deadlock.

A Soma program from the state_machines part of the verified corpus: 62 lines, cells TrafficInterlock, TrafficTests. It passes soma check with soma 3.2.4.

How it is built

The cell TrafficInterlock declares 5 signals in its face: tick(id: String) -> String, emergency(id: String) -> String, clear(id: String) -> String, phase(id: String) -> String and next(id: String) -> List<String>. Its state machine lights starts in ns_go and allows 6 transitions: ns_go -> ns_caution, ns_caution -> ew_go, ew_go -> ew_caution, ew_caution -> ns_go, * -> all_red and all_red -> ns_go. Handlers: tick (line 27), emergency (line 40), clear (line 41), phase (line 42) and next (line 43).

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

Generated from soma describe on this file.

The source

traffic_interlock.cell

// A two-road traffic-light interlock proven safe by construction.
// The single machine cycles ns_go -> ns_caution -> ew_go -> ew_caution
// and back, with an all_red emergency reachable from anywhere. Because no
// state ever has both roads green, the model checker proves the safety
// property `never = []` over a machine that can never deadlock.

cell TrafficInterlock {
    face {
        signal tick(id: String) -> String
        signal emergency(id: String) -> String
        signal clear(id: String) -> String
        signal phase(id: String) -> String
        signal next(id: String) -> List<String>
    }

    state lights {
        initial: ns_go
        ns_go      -> ns_caution
        ns_caution -> ew_go
        ew_go      -> ew_caution
        ew_caution -> ns_go
        *          -> all_red
        all_red    -> ns_go
    }

    // advance one phase in the normal cycle
    on tick(id: String) {
        let cur = get_status(id)
        let nxt = match cur {
            "ns_go"      -> "ns_caution"
            "ns_caution" -> "ew_go"
            "ew_go"      -> "ew_caution"
            "ew_caution" -> "ns_go"
            _            -> "ns_go"
        }
        transition(id, nxt)
        return get_status(id)
    }

    on emergency(id: String) { transition(id, "all_red")  return get_status(id) }
    on clear(id: String)     { transition(id, "ns_go")    return get_status(id) }
    on phase(id: String)     { return get_status(id) }
    on next(id: String)      { return valid_transitions(id) }
}

cell test TrafficTests {
    rules {
        // full normal cycle returns to the start
        assert tick("x") == "ns_caution"
        assert tick("x") == "ew_go"
        assert tick("x") == "ew_caution"
        assert tick("x") == "ns_go"

        // emergency from any phase, then clear back to a green
        assert emergency("y") == "all_red"
        assert clear("y") == "ns_go"

        // illegal: cannot jump green-to-green across roads (skips caution)
        assert_fails transition("z", "ew_go")
    }
}

What the toolchain says

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

$ soma check traffic_interlock.cell
✓ All checks passed.
$ soma test traffic_interlock.cell
test TrafficTests ...
  ✓ assert tick("x") == "ns_caution"
  ✓ assert tick("x") == "ew_go"
  ✓ assert tick("x") == "ew_caution"
  ✓ assert tick("x") == "ns_go"
  ✓ assert emergency("y") == "all_red"
  ✓ assert clear("y") == "ns_go"
  ✓ assert_fails transition("z", "ew_go") — raised invalid transition: ns_go → ew_go. Current state: 'ns_go'. Valid targets: [ns_caution, all_red]

7 tests: 7 passed, 0 failed
$ soma verify traffic_interlock.cell
Verifying traffic_interlock.cell...

State machine 'lights': 5 states, initial 'ns_go'
  States: [all_red, ew_caution, ew_go, ns_caution, ns_go]

  ✓ 5 states, 9 transitions
  ✓ all states reachable from 'ns_go'
  ✓ reactive machine: no terminal state, and every reachable state can return to 'ns_go'
  ✓ no deadlocks
  ✓ liveness: every reachable state can return to 'ns_go' (reactive machine)
  ✓ wildcard transitions: * -> [all_red]
  ⚠ dynamic transition targets: handler `tick` 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 5 handlers structurally terminate
  ⚠ refinement (partial — dynamic targets fall back to runtime check): handler `tick` 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 `tick` ⟶ <dynamic>
  ✓ refinement (partial — dynamic targets fall back to runtime check): handler `emergency` ⟶ {all_red}
  ✓ refinement (partial — dynamic targets fall back to runtime check): handler `clear` ⟶ {ns_go}
  · transition sources: `emergency`, `clear` 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)

10 passed, 2 warnings, 0 failures

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

Builtins used

get_status, transition, valid_transitions

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