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

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.

Generated from soma describe on this file.

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

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

$ 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

Builtins used

get_status, map, transition

Source file: /corpus/energy/wind_overspeed.cell · examples/corpus/energy/wind_overspeed.cell