soma.cell

Fuel crossfeed — Air Transat 236: never feed a leak from the good tank

On 24 August 2001, Air Transat Flight 236 (an A330 over the Atlantic) developed a fuel leak in the right engine's feed line. Following the imbalance checklist, the crew opened the crossfeed valve — and pumped the LEFT tank's fuel into the leak too. Both engines flamed out; the aircraft glided 75 miles, dead-stick, to a runway in the Azores. 306 people survived on the captain's gliding skill, not on the system. The procedure trap: crossfeed is the correct response to an imbalance and the catastrophic response to a leak — and the two look identical on the gauges unless you check totals. Here that check is structural: opening crossfeed REQUIRES a fuel-total reconciliation, a detected leak drives the machine to leak_isolated — a TERMINAL state with no edge to crossfeed_open, ever — and the tank ledger refuses negative or overfull quantities outright.

A Soma program from the aerospace part of the verified corpus: 118 lines, cells FuelCrossfeed, CrossfeedTests. It passes soma check with soma 3.2.4.

How it is built

The cell FuelCrossfeed declares 6 signals in its face: load_tanks(id: String, left_kg: Int, right_kg: Int) -> Map, burn(id: String, side: String, kg: Int) -> Map, open_crossfeed(id: String, expected_total_kg: Int) -> Map, shut_crossfeed(id: String) -> Map, feed_mode(id: String) -> String and tank_total(id: String) -> Int; keeps 1 memory slot: tank_kg (Map<String, Int>, persistent and consistent) under the invariant tank_kg >= 0 && tank_kg <= 7000. Its state machine feed starts in isolated_feed and allows 4 transitions: isolated_feed -> crossfeed_open, crossfeed_open -> isolated_feed, isolated_feed -> leak_isolated and crossfeed_open -> leak_isolated. leak_isolated is terminal. Handlers: load_tanks (line 47), burn (line 55), tank_total (line 62), open_crossfeed (line 69), shut_crossfeed (line 81) and feed_mode (line 87).

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

Generated from soma describe on this file.

The source

fuel_crossfeed.cell

// ============================================================================
// FUEL CROSSFEED — Air Transat 236: never feed a leak from the good tank
// ============================================================================
//
// On 24 August 2001, Air Transat Flight 236 (an A330 over the Atlantic)
// developed a fuel leak in the right engine's feed line. Following the
// imbalance checklist, the crew opened the crossfeed valve — and pumped
// the LEFT tank's fuel into the leak too. Both engines flamed out; the
// aircraft glided 75 miles, dead-stick, to a runway in the Azores. 306
// people survived on the captain's gliding skill, not on the system.
//
// The procedure trap: crossfeed is the correct response to an imbalance
// and the catastrophic response to a leak — and the two look identical
// on the gauges unless you check totals. Here that check is structural:
// opening crossfeed REQUIRES a fuel-total reconciliation, a detected
// leak drives the machine to leak_isolated — a TERMINAL state with no
// edge to crossfeed_open, ever — and the tank ledger refuses negative
// or overfull quantities outright.
// ============================================================================

cell FuelCrossfeed {
    face {
        signal load_tanks(id: String, left_kg: Int, right_kg: Int) -> Map
        signal burn(id: String, side: String, kg: Int) -> Map
        signal open_crossfeed(id: String, expected_total_kg: Int) -> Map
        signal shut_crossfeed(id: String) -> Map
        signal feed_mode(id: String) -> String
        signal tank_total(id: String) -> Int
    }

    memory {
        // per-tank quantity, kg — capacity 7000; a negative tank or an
        // overfill is rejected at the write
        tank_kg: Map<String, Int> [persistent, consistent]
        invariant tank_kg >= 0 && tank_kg <= 7000
    }

    // ABSENT: leak_isolated -> crossfeed_open (the Air Transat valve)
    state feed {
        initial: isolated_feed         // each tank feeds its own engine
        isolated_feed -> crossfeed_open
        crossfeed_open -> isolated_feed
        isolated_feed -> leak_isolated   // terminal
        crossfeed_open -> leak_isolated  // terminal
    }

    on load_tanks(id: String, left_kg_v: Int, right_kg_v: Int) {
        let l = try { tank_kg.set("{id}:L", left_kg_v) }
        if l.error != () { return map("ok", false, "reason", "left tank load refused") }
        let r = try { tank_kg.set("{id}:R", right_kg_v) }
        if r.error != () { return map("ok", false, "reason", "right tank load refused") }
        return map("ok", true, "total", tank_total(id))
    }

    on burn(id: String, side: String, kg: Int) {
        let cur = tank_kg.get("{id}:{side}") ?? 0
        let w = try { tank_kg.set("{id}:{side}", cur - kg) }
        if w.error != () { return map("ok", false, "reason", "tank cannot go negative") }
        return map("ok", true, "remaining", cur - kg)
    }

    on tank_total(id: String) {
        return (tank_kg.get("{id}:L") ?? 0) + (tank_kg.get("{id}:R") ?? 0)
    }

    // Crossfeed opens ONLY after the totals reconcile. Fuel that left the
    // tanks faster than the engines burned it is a LEAK — and the machine
    // goes to leak_isolated instead, permanently.
    on open_crossfeed(id: String, expected_total_kg: Int) {
        let actual = tank_total(id)
        if expected_total_kg - actual > 200 {
            transition(id, "leak_isolated")
            let missing = expected_total_kg - actual
            return map("ok", false, "reason", "LEAK: {missing} kg unaccounted — crossfeed isolated for the flight")
        }
        let r = try { transition(id, "crossfeed_open") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "mode", get_status(id))
    }

    on shut_crossfeed(id: String) {
        let r = try { transition(id, "isolated_feed") }
        if r.error != () { return map("ok", false, "reason", "{r.error}") }
        return map("ok", true, "mode", get_status(id))
    }

    on feed_mode(id: String) { return get_status(id) }
}

cell test CrossfeedTests {
    rules {
        // a plain imbalance: totals reconcile, crossfeed is the right call
        assert load_tanks("f1", 5000, 5000).ok == true
        assert burn("f1", "R", 1500).ok == true        // right engine ran the APU leg
        assert burn("f1", "L", 500).ok == true
        assert open_crossfeed("f1", 8000).ok == true   // 10000 loaded - 2000 burned
        assert feed_mode("f1") == "crossfeed_open"
        assert shut_crossfeed("f1").ok == true

        // THE Air Transat case: 1800 kg simply GONE overboard
        assert load_tanks("f2", 5000, 5000).ok == true
        assert burn("f2", "R", 2300).ok == true        // 500 burned + 1800 leaked
        assert burn("f2", "L", 500).ok == true
        assert open_crossfeed("f2", 9000).ok == false  // books say 9000, tanks hold 7200
        assert feed_mode("f2") == "leak_isolated"

        // leak_isolated is TERMINAL: the valve can never open this flight
        assert_fails transition("f2", "crossfeed_open")
        assert_fails transition("f2", "isolated_feed")
        assert open_crossfeed("f2", 7200).ok == false

        // the ledger itself: no negative tanks, no overfills
        assert_fails tank_kg.set("f1:L", -10)
        assert_fails tank_kg.set("f1:L", 9000)
        assert burn("f1", "L", 99999).ok == false
    }
}

What the toolchain says

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

$ soma check fuel_crossfeed.cell
warning: parameter 2 of `load_tanks`: the face calls it `left_kg`, the handler `left_kg_v` — an HTTP JSON body is matched by NAME (the handler's), and `soma describe` publishes the face's: use one name
  --> fuel_crossfeed.cell:23:9
   |
23 |         signal load_tanks(id: String, left_kg: Int, right_kg: Int) -…
   |         ^
warning: parameter 3 of `load_tanks`: the face calls it `right_kg`, the handler `right_kg_v` — an HTTP JSON body is matched by NAME (the handler's), and `soma describe` publishes the face's: use one name
  --> fuel_crossfeed.cell:23:9
   |
23 |         signal load_tanks(id: String, left_kg: Int, right_kg: Int) -…
   |         ^
✓ 2 warning(s), no errors.
$ soma test fuel_crossfeed.cell
test CrossfeedTests ...
  ✓ assert load_tanks("f1", 5000, 5000).ok == true
  ✓ assert burn("f1", "R", 1500).ok == true
  ✓ assert burn("f1", "L", 500).ok == true
  ✓ assert open_crossfeed("f1", 8000).ok == true
  ✓ assert feed_mode("f1") == "crossfeed_open"
  ✓ assert shut_crossfeed("f1").ok == true
  ✓ assert load_tanks("f2", 5000, 5000).ok == true
  ✓ assert burn("f2", "R", 2300).ok == true
  ✓ assert burn("f2", "L", 500).ok == true
  ✓ assert open_crossfeed("f2", 9000).ok == false
  ✓ assert feed_mode("f2") == "leak_isolated"
  ✓ assert_fails transition("f2", "crossfeed_open") — raised invalid transition: leak_isolated → crossfeed_open. Current state: 'leak_isolated'. Valid targets: []
  ✓ assert_fails transition("f2", "isolated_feed") — raised invalid transition: leak_isolated → isolated_feed. Current state: 'leak_isolated'. Valid targets: []
  ✓ assert open_crossfeed("f2", 7200).ok == false
  ✓ assert_fails tank_kg.set("f1:L", -10) — raised memory invariant violated on 'tank_kg': tank_kg >= 0 && tank_kg <= 7000 — rejected write of -10 (key "f1:L"); the slot is unchanged
  ✓ assert_fails tank_kg.set("f1:L", 9000) — raised memory invariant violated on 'tank_kg': tank_kg >= 0 && tank_kg <= 7000 — rejected write of 9000 (key "f1:L"); the slot is unchanged
  ✓ assert burn("f1", "L", 99999).ok == false

17 tests: 17 passed, 0 failed
$ soma verify fuel_crossfeed.cell
Verifying fuel_crossfeed.cell...

State machine 'feed': 3 states, initial 'isolated_feed'
  States: [crossfeed_open, isolated_feed, leak_isolated]

  ✓ 3 states, 4 transitions
  ✓ all states reachable from 'isolated_feed'
  ✓ terminal states: [leak_isolated]
  ✓ no deadlocks
  ✓ liveness: every state can eventually reach a terminal state
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (2 handlers, 3 literal transitions, 0 dynamic)
  ✓ termination: all 6 handlers structurally terminate
  ✓ refinement: handler `open_crossfeed` ⟶ {leak_isolated [if expected_total_kg - actual > 200], crossfeed_open}
  ✓ refinement: handler `shut_crossfeed` ⟶ {isolated_feed}
  · transition sources: `open_crossfeed`, `shut_crossfeed` 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)

FuelCrossfeed/invariants:

  ⚠ invariant tank_kg >= 0 && tank_kg <= 7000 — runtime-checked (computed values): burn → tank_kg because `kg` is a parameter (narrow it: `require (cur - kg) >= 0 && (cur - kg) <= 7000 else …`), load_tanks → tank_kg because `left_kg_v` is a parameter (narrow it: `require left_kg_v >= 0 && left_kg_v <= 7000 else …`), load_tanks → tank_kg because `right_kg_v` is a parameter (narrow it: `require right_kg_v >= 0 && right_kg_v <= 7000 else …`)

9 passed, 1 warnings, 0 failures

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

Builtins used

get_status, map, transition

Source file: /corpus/aerospace/fuel_crossfeed.cell · examples/corpus/aerospace/fuel_crossfeed.cell