Wind turbine overspeed — after an emergency brake, inspection or nothing
February 22, 2008, Hornslet, Denmark: a 600 kW turbine's brakes failed during a storm and the machine spun until it tore itself apart — blades and nacelle thrown hundreds of meters, the disintegration caught on a video that every wind technician has since been shown. A rotor past its design speed stores energy in exactly the form that kills: blade root fatigue you cannot see from the ground. That is why an overspeed event is never "reset and resume". Here the rule is the machine: from `storm_brake` the ONLY exit is `inspection` — there is no path back to `running` that skips a human at the blade roots. A turbine the inspection condemns is `condemned`, terminal, forever; soma verify proves both. The rotor-speed slot is the inner wall: no write can record an rpm past the 25-rpm design limit, so the telemetry layer cannot even pretend an overspeed was normal.
A Soma program from the energy part of the verified corpus: 136 lines, cells Turbine, OverspeedTests. It passes soma check with soma 3.2.4.
How it is built
The cell Turbine declares 8 signals in its face: start_rotor(id: String) -> Map, normal_stop(id: String) -> Map, emergency_brake(id: String) -> Map, begin_inspection(id: String) -> Map, inspection_passed(id: String) -> Map, condemn_turbine(id: String) -> Map, log_rpm(id: String, rpm: Int) -> Map and turbine_state(id: String) -> String; keeps 1 memory slot: rotor_rpm (Map<String, Int>, persistent and consistent) under the invariant rotor_rpm >= 0 && rotor_rpm <= 25. Its state machine rotor starts in parked and allows 6 transitions: parked -> running, running -> parked, running -> storm_brake, storm_brake -> inspection, inspection -> parked and inspection -> condemned. condemned is terminal. Handlers: start_rotor (line 49), normal_stop (line 55), emergency_brake (line 62), begin_inspection (line 69), inspection_passed (line 75), condemn_turbine (line 81), log_rpm (line 87) and turbine_state (line 98).
The file also holds 1 test cell (OverspeedTests) with 17 assertions, run by soma test.
The source
wind_overspeed.cell
// ============================================================================
// WIND TURBINE OVERSPEED — after an emergency brake, inspection or nothing
// ============================================================================
//
// February 22, 2008, Hornslet, Denmark: a 600 kW turbine's brakes failed
// during a storm and the machine spun until it tore itself apart — blades
// and nacelle thrown hundreds of meters, the disintegration caught on a
// video that every wind technician has since been shown. A rotor past its
// design speed stores energy in exactly the form that kills: blade root
// fatigue you cannot see from the ground.
//
// That is why an overspeed event is never "reset and resume". Here the
// rule is the machine: from `storm_brake` the ONLY exit is `inspection` —
// there is no path back to `running` that skips a human at the blade
// roots. A turbine the inspection condemns is `condemned`, terminal,
// forever; soma verify proves both. The rotor-speed slot is the inner
// wall: no write can record an rpm past the 25-rpm design limit, so the
// telemetry layer cannot even pretend an overspeed was normal.
// ============================================================================
cell Turbine {
face {
signal start_rotor(id: String) -> Map
signal normal_stop(id: String) -> Map
signal emergency_brake(id: String) -> Map
signal begin_inspection(id: String) -> Map
signal inspection_passed(id: String) -> Map
signal condemn_turbine(id: String) -> Map
signal log_rpm(id: String, rpm: Int) -> Map
signal turbine_state(id: String) -> String
}
memory {
// last commanded/recorded rotor speed; design limit 25 rpm
rotor_rpm: Map<String, Int> [persistent, consistent]
invariant rotor_rpm >= 0 && rotor_rpm <= 25
}
state rotor {
initial: parked
parked -> running
running -> parked // normal feathered stop
running -> storm_brake // overspeed / storm emergency stop
storm_brake -> inspection // the ONLY exit: a human at the blade roots
inspection -> parked // structure passed — may run again
inspection -> condemned // cracks found — TERMINAL, forever
}
on start_rotor(id: String) {
let r = try { transition(id, "running") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(id))
}
on normal_stop(id: String) {
let r = try { transition(id, "parked") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
rotor_rpm.set(id, 0)
return map("ok", true, "state", get_status(id))
}
on emergency_brake(id: String) {
let r = try { transition(id, "storm_brake") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
rotor_rpm.set(id, 0)
return map("ok", true, "state", get_status(id))
}
on begin_inspection(id: String) {
let r = try { transition(id, "inspection") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(id))
}
on inspection_passed(id: String) {
let r = try { transition(id, "parked") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(id))
}
on condemn_turbine(id: String) {
let r = try { transition(id, "condemned") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(id))
}
on log_rpm(id: String, rpm: Int) {
if get_status(id) != "running" {
return map("ok", false, "reason", "rotor not running (state: {get_status(id)})")
}
let w = try { rotor_rpm.set(id, rpm) }
if w.error != () {
return map("ok", false, "reason", "design-speed wall: {w.error}")
}
return map("ok", true, "rpm", rpm)
}
on turbine_state(id: String) { return get_status(id) }
}
cell test OverspeedTests {
rules {
// normal operation
assert start_rotor("t1").ok == true
assert log_rpm("t1", 18).ok == true
// telemetry cannot even RECORD a past-limit rpm as routine
assert_fails rotor_rpm.set("t1", 40)
assert log_rpm("t1", 40).ok == false
// storm: emergency brake fires
assert emergency_brake("t1").ok == true
// THE HORNSLET MOVE: reset the brake and spin back up.
// No such transition exists.
assert_fails transition("t1", "running")
assert start_rotor("t1").ok == false
assert_fails transition("t1", "parked")
// inspection passes — only then does the turbine run again
assert begin_inspection("t1").ok == true
assert inspection_passed("t1").ok == true
assert start_rotor("t1").ok == true
// a second event, and this time the blade roots are cracked
assert emergency_brake("t1").ok == true
assert begin_inspection("t1").ok == true
assert condemn_turbine("t1").ok == true
// condemned is TERMINAL: this machine never turns again
assert_fails transition("t1", "parked")
assert_fails transition("t1", "running")
assert turbine_state("t1") == "condemned"
}
}What the toolchain says
$ soma check wind_overspeed.cell
✓ All checks passed.$ soma test wind_overspeed.cell
test OverspeedTests ...
✓ assert start_rotor("t1").ok == true
✓ assert log_rpm("t1", 18).ok == true
✓ assert_fails rotor_rpm.set("t1", 40) — raised memory invariant violated on 'rotor_rpm': rotor_rpm >= 0 && rotor_rpm <= 25 — rejected write of 40 (key "t1"); the slot is unchanged
✓ assert log_rpm("t1", 40).ok == false
✓ assert emergency_brake("t1").ok == true
✓ assert_fails transition("t1", "running") — raised invalid transition: storm_brake → running. Current state: 'storm_brake'. Valid targets: [inspection]
✓ assert start_rotor("t1").ok == false
✓ assert_fails transition("t1", "parked") — raised invalid transition: storm_brake → parked. Current state: 'storm_brake'. Valid targets: [inspection]
✓ assert begin_inspection("t1").ok == true
✓ assert inspection_passed("t1").ok == true
✓ assert start_rotor("t1").ok == true
✓ assert emergency_brake("t1").ok == true
✓ assert begin_inspection("t1").ok == true
✓ assert condemn_turbine("t1").ok == true
✓ assert_fails transition("t1", "parked") — raised invalid transition: condemned → parked. Current state: 'condemned'. Valid targets: []
✓ assert_fails transition("t1", "running") — raised invalid transition: condemned → running. Current state: 'condemned'. Valid targets: []
✓ assert turbine_state("t1") == "condemned"
17 tests: 17 passed, 0 failed$ soma verify wind_overspeed.cell
Verifying wind_overspeed.cell...
State machine 'rotor': 5 states, initial 'parked'
States: [condemned, inspection, parked, running, storm_brake]
✓ 5 states, 6 transitions
✓ all states reachable from 'parked'
✓ terminal states: [condemned]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
✓ think-isolated: CTL safety properties hold regardless of LLM output (6 handlers, 6 literal transitions, 0 dynamic)
✓ termination: all 8 handlers structurally terminate
✓ refinement: handler `start_rotor` ⟶ {running}
✓ refinement: handler `normal_stop` ⟶ {parked}
✓ refinement: handler `emergency_brake` ⟶ {storm_brake}
✓ refinement: handler `begin_inspection` ⟶ {inspection}
✓ refinement: handler `inspection_passed` ⟶ {parked}
✓ refinement: handler `condemn_turbine` ⟶ {condemned}
· transition sources: `start_rotor`, `normal_stop`, `emergency_brake`, `begin_inspection`, `inspection_passed`, `condemn_turbine` 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)
Turbine/invariants:
✓ invariant rotor_rpm >= 0 && rotor_rpm <= 25 — writer 'emergency_brake' proven (writes 0)
✓ invariant rotor_rpm >= 0 && rotor_rpm <= 25 — writer 'normal_stop' proven (writes 0)
⚠ invariant rotor_rpm >= 0 && rotor_rpm <= 25 — runtime-checked (computed values): log_rpm → rotor_rpm because `rpm` is a parameter (narrow it: `require rpm >= 0 && rpm <= 25 else …`)
15 passed, 1 warnings, 0 failures
Temporal properties for 'rotor':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK