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