soma.cell

Mine hoist — no motion with the gate open, and a trip is forever

Markham Colliery, 1973: the hoist brake failed and the cage carrying 29 miners ran away down the shaft — 18 dead. Overwind and overspeed arrestors exist because a winder must be STOPPED by hardware, not promised to stop by software. Two structural walls here: the cage can only wind with its gates sealed (`gates_open -> winding` does not exist, and a moving cage cannot open its gates mid-shaft), and the overspeed trip is TERMINAL — soma verify proves `tripped` has no exit, so no command sequence ever winds a tripped hoist again. The speed ledger refuses any value past the 12 m/s arrestor setting.

A Soma program from the safety_interlocks part of the verified corpus: 86 lines, cells HoistControl, HoistTests. It passes soma check with soma 3.2.4.

How it is built

The cell HoistControl declares 5 signals in its face: seal_gates(id: String) -> String, open_gates(id: String) -> String, wind(id: String, mps: Int) -> Map, arrive(id: String) -> String and overspeed_trip(id: String) -> String; keeps 1 memory slot: cage_mps (Map<String, Int>, persistent and consistent) under the invariant cage_mps >= 0 && cage_mps <= 12. Its state machine winder starts in parked_gates_open and allows 5 transitions: parked_gates_open -> parked_sealed, parked_sealed -> parked_gates_open, parked_sealed -> winding, winding -> parked_sealed and winding -> tripped. tripped is terminal. Handlers: seal_gates (line 36), open_gates (line 37), wind (line 39), arrive (line 52) and overspeed_trip (line 58).

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

Generated from soma describe on this file.

The source

mine_hoist.cell

// MINE HOIST — no motion with the gate open, and a trip is forever.
// Markham Colliery, 1973: the hoist brake failed and the cage carrying
// 29 miners ran away down the shaft — 18 dead. Overwind and overspeed
// arrestors exist because a winder must be STOPPED by hardware, not
// promised to stop by software. Two structural walls here: the cage can
// only wind with its gates sealed (`gates_open -> winding` does not
// exist, and a moving cage cannot open its gates mid-shaft), and the
// overspeed trip is TERMINAL — soma verify proves `tripped` has no
// exit, so no command sequence ever winds a tripped hoist again.
// The speed ledger refuses any value past the 12 m/s arrestor setting.

cell HoistControl {
    face {
        signal seal_gates(id: String) -> String
        signal open_gates(id: String) -> String
        signal wind(id: String, mps: Int) -> Map
        signal arrive(id: String) -> String
        signal overspeed_trip(id: String) -> String
    }

    memory {
        // cage speed, m/s — the arrestor setting is the wall
        cage_mps: Map<String, Int> [persistent, consistent]
        invariant cage_mps >= 0 && cage_mps <= 12
    }

    state winder {
        initial: parked_gates_open
        parked_gates_open -> parked_sealed
        parked_sealed -> parked_gates_open
        parked_sealed -> winding            // motion ONLY behind sealed gates
        winding -> parked_sealed            // stop first, then unlock
        winding -> tripped                  // overspeed/overwind: terminal
    }

    on seal_gates(id: String) { transition(id, "parked_sealed")  return get_status(id) }
    on open_gates(id: String) { transition(id, "parked_gates_open")  return get_status(id) }

    on wind(id: String, mps: Int) {
        let r = try { transition(id, "winding") }
        if r.error != () {
            return map("ok", false, "reason", "gates not proven sealed")
        }
        let w = try { cage_mps.set(id, mps) }
        if w.error != () {
            transition(id, "tripped")       // demanding > 12 m/s IS the trip
            return map("ok", false, "state", get_status(id), "reason", "overspeed demand {mps} m/s")
        }
        return map("ok", true, "state", get_status(id), "mps", mps)
    }

    on arrive(id: String) {
        cage_mps.set(id, 0)
        transition(id, "parked_sealed")
        return get_status(id)
    }

    on overspeed_trip(id: String) { transition(id, "tripped")  return get_status(id) }
}

cell test HoistTests {
    rules {
        // motion with the gate open: the edge does not exist
        assert_fails transition("h1", "winding")

        // the legitimate cycle: seal, wind, arrive, unlock
        assert seal_gates("h1") == "parked_sealed"
        assert wind("h1", 10).ok == true
        // a moving cage cannot open its gates mid-shaft
        assert_fails transition("h1", "parked_gates_open")
        assert arrive("h1") == "parked_sealed"
        assert open_gates("h1") == "parked_gates_open"

        // the speed ledger refuses anything past the arrestor
        assert_fails cage_mps.set("h1", 13)
        assert_fails cage_mps.set("h1", -1)

        // an overspeed demand trips the winder — PERMANENTLY
        assert seal_gates("h2") == "parked_sealed"
        assert wind("h2", 40).ok == false
        assert_fails transition("h2", "parked_sealed")
        assert_fails transition("h2", "winding")
        assert wind("h2", 5).ok == false
    }
}

What the toolchain says

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

$ soma check mine_hoist.cell
✓ All checks passed.
$ soma test mine_hoist.cell
test HoistTests ...
  ✓ assert_fails transition("h1", "winding") — raised invalid transition: parked_gates_open → winding. Current state: 'parked_gates_open'. Valid targets: [parked_sealed]
  ✓ assert seal_gates("h1") == "parked_sealed"
  ✓ assert wind("h1", 10).ok == true
  ✓ assert_fails transition("h1", "parked_gates_open") — raised invalid transition: winding → parked_gates_open. Current state: 'winding'. Valid targets: [parked_sealed, tripped]
  ✓ assert arrive("h1") == "parked_sealed"
  ✓ assert open_gates("h1") == "parked_gates_open"
  ✓ assert_fails cage_mps.set("h1", 13) — raised memory invariant violated on 'cage_mps': cage_mps >= 0 && cage_mps <= 12 — rejected write of 13 (key "h1"); the slot is unchanged
  ✓ assert_fails cage_mps.set("h1", -1) — raised memory invariant violated on 'cage_mps': cage_mps >= 0 && cage_mps <= 12 — rejected write of -1 (key "h1"); the slot is unchanged
  ✓ assert seal_gates("h2") == "parked_sealed"
  ✓ assert wind("h2", 40).ok == false
  ✓ assert_fails transition("h2", "parked_sealed") — raised invalid transition: tripped → parked_sealed. Current state: 'tripped'. Valid targets: []
  ✓ assert_fails transition("h2", "winding") — raised invalid transition: tripped → winding. Current state: 'tripped'. Valid targets: []
  ✓ assert wind("h2", 5).ok == false

13 tests: 13 passed, 0 failed
$ soma verify mine_hoist.cell
Verifying mine_hoist.cell...

State machine 'winder': 4 states, initial 'parked_gates_open'
  States: [parked_gates_open, parked_sealed, tripped, winding]

  ✓ 4 states, 5 transitions
  ✓ all states reachable from 'parked_gates_open'
  ✓ terminal states: [tripped]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (5 handlers, 6 literal transitions, 0 dynamic)
  ✓ termination: all 5 handlers structurally terminate
  ✓ refinement: handler `seal_gates` ⟶ {parked_sealed}
  ✓ refinement: handler `open_gates` ⟶ {parked_gates_open}
  ✓ refinement: handler `wind` ⟶ {winding, tripped [if w.error != ()]}
  ✓ refinement: handler `arrive` ⟶ {parked_sealed}
  ✓ refinement: handler `overspeed_trip` ⟶ {tripped}
  · transition sources: `seal_gates`, `open_gates`, `wind`, `arrive`, `overspeed_trip` 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)

HoistControl/invariants:

  ✓ invariant cage_mps >= 0 && cage_mps <= 12 — writer 'arrive' proven (writes 0)
  ⚠ invariant cage_mps >= 0 && cage_mps <= 12 — runtime-checked (computed values): wind → cage_mps because `mps` is a parameter (narrow it: `require mps >= 0 && mps <= 12 else …`)

13 passed, 1 warnings, 0 failures

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

Builtins used

get_status, map, transition

Source file: /corpus/safety_interlocks/mine_hoist.cell · examples/corpus/safety_interlocks/mine_hoist.cell