soma.cell

Spillway gates — a dam may never lose its last release path

February 2017, Oroville, California: the tallest dam in the US lost its main spillway to erosion mid-flood, the emergency spillway began to fail within hours of first use, and 188,000 people downstream were evacuated. The lesson generalized by every dam-safety review since: release capacity is life-safety equipment, and maintenance scheduling is where it silently disappears — gate 3 is down for seal replacement, gate 5's hoist motor is out, a contractor asks to isolate gate 7 "just for the afternoon", and suddenly the dam cannot pass the inflow it was built for. Two structural walls below. The fleet ledger `gates_avail` carries an invariant floor of ONE: the write that would take the last operable gate out of service is rejected by the memory, whatever the work order says. And each gate's overhaul is a state machine: an overhauled gate returns to service only through `wet_tested` — an untested gate back on the flood roster is a transition that does not exist. soma verify proves it.

A Soma program from the energy part of the verified corpus: 121 lines, cells Spillway, SpillwayTests. It passes soma check with soma 3.2.4.

How it is built

The cell Spillway declares 6 signals in its face: isolate_gate(dam: String, gate: String) -> Map, begin_overhaul(gate: String) -> Map, finish_overhaul(gate: String) -> Map, wet_test_passed(dam: String, gate: String) -> Map, gates_in_service(dam: String) -> Int and gate_state(gate: String) -> String; keeps 1 memory slot: gates_avail (Map<String, Int>, persistent and consistent) under the invariant gates_avail >= 1 && gates_avail <= 8. Its state machine overhaul starts in in_service and allows 5 transitions: in_service -> isolated, isolated -> stripped, stripped -> reassembled, reassembled -> wet_tested and wet_tested -> in_service. Handlers: gates_in_service (line 47), isolate_gate (line 49), begin_overhaul (line 64), finish_overhaul (line 70), wet_test_passed (line 76) and gate_state (line 85).

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

Generated from soma describe on this file.

The source

spillway_gates.cell

// ============================================================================
// SPILLWAY GATES — a dam may never lose its last release path
// ============================================================================
//
// February 2017, Oroville, California: the tallest dam in the US lost its
// main spillway to erosion mid-flood, the emergency spillway began to fail
// within hours of first use, and 188,000 people downstream were evacuated.
// The lesson generalized by every dam-safety review since: release
// capacity is life-safety equipment, and maintenance scheduling is where
// it silently disappears — gate 3 is down for seal replacement, gate 5's
// hoist motor is out, a contractor asks to isolate gate 7 "just for the
// afternoon", and suddenly the dam cannot pass the inflow it was built for.
//
// Two structural walls below. The fleet ledger `gates_avail` carries an
// invariant floor of ONE: the write that would take the last operable gate
// out of service is rejected by the memory, whatever the work order says.
// And each gate's overhaul is a state machine: an overhauled gate returns
// to service only through `wet_tested` — an untested gate back on the
// flood roster is a transition that does not exist. soma verify proves it.
// ============================================================================

cell Spillway {
    face {
        signal isolate_gate(dam: String, gate: String) -> Map
        signal begin_overhaul(gate: String) -> Map
        signal finish_overhaul(gate: String) -> Map
        signal wet_test_passed(dam: String, gate: String) -> Map
        signal gates_in_service(dam: String) -> Int
        signal gate_state(gate: String) -> String
    }

    memory {
        // operable gates per dam — the floor of 1 is the life-safety wall
        gates_avail: Map<String, Int> [persistent, consistent]
        invariant gates_avail >= 1 && gates_avail <= 8
    }

    state overhaul {
        initial: in_service
        in_service -> isolated     // dewatered, stop-logs in, hoist locked
        isolated -> stripped       // seals/hoist under overhaul
        stripped -> reassembled
        reassembled -> wet_tested  // full-travel test UNDER HEAD
        wet_tested -> in_service   // only a tested gate rejoins the roster
    }

    on gates_in_service(dam: String) { return gates_avail.get(dam) ?? 8 }

    on isolate_gate(dam: String, gate: String) {
        // the fleet wall first: taking this gate may not strand the dam
        let w = try { gates_avail.set(dam, gates_in_service(dam) - 1) }
        if w.error != () {
            return map("ok", false, "reason",
                "refused: would leave {dam} without an operable release path")
        }
        let r = try { transition(gate, "isolated") }
        if r.error != () {
            gates_avail.set(dam, gates_in_service(dam) + 1)   // roll back
            return map("ok", false, "reason", "{r.error}")
        }
        return map("ok", true, "gates_in_service", gates_in_service(dam))
    }

    on begin_overhaul(gate: String) {
        let r = try { transition(gate, "stripped") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "state", get_status(gate))
    }

    on finish_overhaul(gate: String) {
        let r = try { transition(gate, "reassembled") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "state", get_status(gate))
    }

    on wet_test_passed(dam: String, gate: String) {
        let r = try { transition(gate, "wet_tested") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        transition(gate, "in_service")
        gates_avail.set(dam, gates_in_service(dam) + 1)
        return map("ok", true, "state", get_status(gate),
                   "gates_in_service", gates_in_service(dam))
    }

    on gate_state(gate: String) { return get_status(gate) }
}

cell test SpillwayTests {
    rules {
        // the dam starts with 8 operable gates; isolate seven of them
        assert isolate_gate("oro", "g1").ok == true
        assert isolate_gate("oro", "g2").ok == true
        assert isolate_gate("oro", "g3").ok == true
        assert isolate_gate("oro", "g4").ok == true
        assert isolate_gate("oro", "g5").ok == true
        assert isolate_gate("oro", "g6").ok == true
        assert isolate_gate("oro", "g7").ok == true
        assert gates_in_service("oro") == 1

        // THE LAST GATE: the work order that strands the dam is refused
        assert isolate_gate("oro", "g8").ok == false
        assert gates_in_service("oro") == 1
        assert gate_state("g8") == "in_service"
        assert_fails gates_avail.set("oro", 0)

        // an overhauled gate cannot rejoin the roster untested:
        // reassembled -> in_service is not a transition
        assert begin_overhaul("g1").ok == true
        assert finish_overhaul("g1").ok == true
        assert_fails transition("g1", "in_service")

        // tested under head, it returns — and frees the next work order
        assert wet_test_passed("oro", "g1").ok == true
        assert gates_in_service("oro") == 2
        assert isolate_gate("oro", "g8").ok == true

        // a gate in service cannot be "stripped" without isolation
        assert_fails transition("g1", "stripped")
    }
}

What the toolchain says

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

$ soma check spillway_gates.cell
✓ All checks passed.
$ soma test spillway_gates.cell
test SpillwayTests ...
  ✓ assert isolate_gate("oro", "g1").ok == true
  ✓ assert isolate_gate("oro", "g2").ok == true
  ✓ assert isolate_gate("oro", "g3").ok == true
  ✓ assert isolate_gate("oro", "g4").ok == true
  ✓ assert isolate_gate("oro", "g5").ok == true
  ✓ assert isolate_gate("oro", "g6").ok == true
  ✓ assert isolate_gate("oro", "g7").ok == true
  ✓ assert gates_in_service("oro") == 1
  ✓ assert isolate_gate("oro", "g8").ok == false
  ✓ assert gates_in_service("oro") == 1
  ✓ assert gate_state("g8") == "in_service"
  ✓ assert_fails gates_avail.set("oro", 0) — raised memory invariant violated on 'gates_avail': gates_avail >= 1 && gates_avail <= 8 — rejected write of 0 (key "oro"); the slot is unchanged
  ✓ assert begin_overhaul("g1").ok == true
  ✓ assert finish_overhaul("g1").ok == true
  ✓ assert_fails transition("g1", "in_service") — raised invalid transition: reassembled → in_service. Current state: 'reassembled'. Valid targets: [wet_tested]
  ✓ assert wet_test_passed("oro", "g1").ok == true
  ✓ assert gates_in_service("oro") == 2
  ✓ assert isolate_gate("oro", "g8").ok == true
  ✓ assert_fails transition("g1", "stripped") — raised invalid transition: in_service → stripped. Current state: 'in_service'. Valid targets: [isolated]

19 tests: 19 passed, 0 failed
$ soma verify spillway_gates.cell
Verifying spillway_gates.cell...

State machine 'overhaul': 5 states, initial 'in_service'
  States: [in_service, isolated, reassembled, stripped, wet_tested]

  ✓ 5 states, 5 transitions
  ✓ all states reachable from 'in_service'
  ✓ reactive machine: no terminal state, and every reachable state can return to 'in_service'
  ✓ no deadlocks
  ✓ liveness: every reachable state can return to 'in_service' (reactive machine)
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (4 handlers, 5 literal transitions, 0 dynamic)
  ✓ termination: all 6 handlers structurally terminate
  ✓ refinement: handler `isolate_gate` ⟶ {isolated}
  ✓ refinement: handler `begin_overhaul` ⟶ {stripped}
  ✓ refinement: handler `finish_overhaul` ⟶ {reassembled}
  ✓ refinement: handler `wet_test_passed` ⟶ {wet_tested, in_service}
  · transition sources: `isolate_gate`, `begin_overhaul`, `finish_overhaul`, `wet_test_passed` 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)

Spillway/invariants:

  ✓ invariant gates_avail <= 8 — writer 'isolate_gate' proven (writes gates_in_service(dam) - 1)
  ✓ invariant gates_avail >= 1 — writer 'isolate_gate' proven (writes gates_in_service(dam) + 1)
  ✓ invariant gates_avail >= 1 — writer 'wet_test_passed' proven (writes gates_in_service(dam) + 1)
  ⚠ invariant gates_avail >= 1 && gates_avail <= 8 — runtime-checked (computed values): isolate_gate → gates_avail [gates_avail >= 1] because `gates_in_service(dam) - 1` is only known to lie in [0, 7] — narrow it: `require (gates_in_service(dam) - 1) >= 1 else …`, isolate_gate → gates_avail [gates_avail <= 8] because `gates_in_service(dam) + 1` is only known to lie in [2, 9] — narrow it: `require (gates_in_service(dam) + 1) <= 8 else …`, wet_test_passed → gates_avail [gates_avail <= 8] because `gates_in_service(dam) + 1` is only known to lie in [2, 9] — narrow it: `require (gates_in_service(dam) + 1) <= 8 else …`

14 passed, 1 warnings, 0 failures

  Temporal properties for 'overhaul':
  ✓ 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/spillway_gates.cell · examples/corpus/energy/spillway_gates.cell