Engine fire bottle — one-shot suppression, fuel off FIRST
An engine fire bottle is halon under pressure, and it is ONE-SHOT. Twin-engine ETOPS aircraft carry exactly two per engine; when they are gone, the crew's remaining option is to land. Two procedural truths are written in blood across decades of engine-fire accidents: 1. Pull the fire handle (cut fuel and hydraulics) BEFORE firing the bottle. Halon discharged into a fuel-fed fire is wasted — the fire relights, and now you have one bottle and a burning wing. 2. Never let the controller believe a discharged bottle can fire again. A "suppression available" indication backed by an empty bottle is how crews delay the diversion that would have saved them. Here both rules are the machine's shape: the bottle states are reachable only THROUGH fuel_shutoff, and bottles_exhausted is TERMINAL — soma verify proves no path refills a fired bottle.
A Soma program from the aerospace part of the verified corpus: 117 lines, cells FireSuppression, BottleTests. It passes soma check with soma 3.2.4.
How it is built
The cell FireSuppression declares 5 signals in its face: fire_warning_on(id: String) -> Map, warning_clears(id: String) -> Map, pull_handle(id: String) -> Map, discharge_bottle(id: String) -> Map and suppression_state(id: String) -> String; keeps 1 memory slot: bottles (Map<String, Int>, persistent and consistent) under the invariant bottles >= 0 && bottles <= 2. Its state machine suppression starts in monitoring and allows 5 transitions: monitoring -> fire_warning, fire_warning -> monitoring, fire_warning -> fuel_shutoff, fuel_shutoff -> bottle_one_fired and bottle_one_fired -> bottles_exhausted. bottles_exhausted is terminal. Handlers: fire_warning_on (line 49), warning_clears (line 56), pull_handle (line 62), discharge_bottle (line 68) and suppression_state (line 78).
The file also holds 1 test cell (BottleTests) with 17 assertions, run by soma test.
The source
fire_bottle.cell
// ============================================================================
// ENGINE FIRE BOTTLE — one-shot suppression, fuel off FIRST
// ============================================================================
//
// An engine fire bottle is halon under pressure, and it is ONE-SHOT.
// Twin-engine ETOPS aircraft carry exactly two per engine; when they are
// gone, the crew's remaining option is to land. Two procedural truths
// are written in blood across decades of engine-fire accidents:
//
// 1. Pull the fire handle (cut fuel and hydraulics) BEFORE firing the
// bottle. Halon discharged into a fuel-fed fire is wasted — the
// fire relights, and now you have one bottle and a burning wing.
// 2. Never let the controller believe a discharged bottle can fire
// again. A "suppression available" indication backed by an empty
// bottle is how crews delay the diversion that would have saved them.
//
// Here both rules are the machine's shape: the bottle states are
// reachable only THROUGH fuel_shutoff, and bottles_exhausted is
// TERMINAL — soma verify proves no path refills a fired bottle.
// ============================================================================
cell FireSuppression {
face {
signal fire_warning_on(id: String) -> Map
signal warning_clears(id: String) -> Map
signal pull_handle(id: String) -> Map
signal discharge_bottle(id: String) -> Map
signal suppression_state(id: String) -> String
}
memory {
// bottles remaining on this engine: 0, 1, or 2 — never more
bottles: Map<String, Int> [persistent, consistent]
invariant bottles >= 0 && bottles <= 2
}
// ABSENT: monitoring/fire_warning -> bottle states (no discharge
// into a fuel-fed fire — the handle comes first)
// ABSENT: any edge out of bottles_exhausted (empty means empty)
state suppression {
initial: monitoring
monitoring -> fire_warning
fire_warning -> monitoring // false alarm, loop resets
fire_warning -> fuel_shutoff // handle pulled: fuel + hyd cut
fuel_shutoff -> bottle_one_fired
bottle_one_fired -> bottles_exhausted // terminal: both gone
}
on fire_warning_on(id: String) {
if bottles.get(id) == () { bottles.set(id, 2) }
let r = try { transition(id, "fire_warning") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(id))
}
on warning_clears(id: String) {
let r = try { transition(id, "monitoring") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(id))
}
on pull_handle(id: String) {
let r = try { transition(id, "fuel_shutoff") }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
return map("ok", true, "state", get_status(id), "fuel", "CUT")
}
on discharge_bottle(id: String) {
let left = bottles.get(id) ?? 0
let target = "bottle_one_fired"
if left == 1 { target = "bottles_exhausted" }
let r = try { transition(id, target) }
if r.error != () { return map("ok", false, "reason", "{r.error}") }
bottles.set(id, left - 1)
return map("ok", true, "state", get_status(id), "bottles_left", left - 1)
}
on suppression_state(id: String) { return get_status(id) }
}
cell test BottleTests {
rules {
// discharging with no fire and no handle: structurally absent
assert_fails transition("eng1", "bottle_one_fired")
// fire! — but the bottle BEFORE the handle is also absent:
// halon into a fuel-fed fire is the wasted-bottle accident
assert fire_warning_on("eng1").ok == true
assert_fails transition("eng1", "bottle_one_fired")
assert discharge_bottle("eng1").ok == false
// handle first (fuel cut), then bottle one
assert pull_handle("eng1").ok == true
assert discharge_bottle("eng1").bottles_left == 1
assert suppression_state("eng1") == "bottle_one_fired"
// bottle two — and now the engine's suppression is EXHAUSTED
assert discharge_bottle("eng1").bottles_left == 0
assert suppression_state("eng1") == "bottles_exhausted"
// terminal: no path makes an empty bottle full again
assert_fails transition("eng1", "bottle_one_fired")
assert_fails transition("eng1", "monitoring")
assert discharge_bottle("eng1").ok == false
// the inventory itself cannot lie: 3 bottles or -1 bottles
// are unrepresentable
assert_fails bottles.set("eng1", 3)
assert_fails bottles.set("eng1", -1)
// a false alarm on another engine resets cleanly
assert fire_warning_on("eng2").ok == true
assert warning_clears("eng2").ok == true
assert suppression_state("eng2") == "monitoring"
}
}What the toolchain says
$ soma check fire_bottle.cell
✓ All checks passed.$ soma test fire_bottle.cell
test BottleTests ...
✓ assert_fails transition("eng1", "bottle_one_fired") — raised invalid transition: monitoring → bottle_one_fired. Current state: 'monitoring'. Valid targets: [fire_warning]
✓ assert fire_warning_on("eng1").ok == true
✓ assert_fails transition("eng1", "bottle_one_fired") — raised invalid transition: fire_warning → bottle_one_fired. Current state: 'fire_warning'. Valid targets: [monitoring, fuel_shutoff]
✓ assert discharge_bottle("eng1").ok == false
✓ assert pull_handle("eng1").ok == true
✓ assert discharge_bottle("eng1").bottles_left == 1
✓ assert suppression_state("eng1") == "bottle_one_fired"
✓ assert discharge_bottle("eng1").bottles_left == 0
✓ assert suppression_state("eng1") == "bottles_exhausted"
✓ assert_fails transition("eng1", "bottle_one_fired") — raised invalid transition: bottles_exhausted → bottle_one_fired. Current state: 'bottles_exhausted'. Valid targets: []
✓ assert_fails transition("eng1", "monitoring") — raised invalid transition: bottles_exhausted → monitoring. Current state: 'bottles_exhausted'. Valid targets: []
✓ assert discharge_bottle("eng1").ok == false
✓ assert_fails bottles.set("eng1", 3) — raised memory invariant violated on 'bottles': bottles >= 0 && bottles <= 2 — rejected write of 3 (key "eng1"); the slot is unchanged
✓ assert_fails bottles.set("eng1", -1) — raised memory invariant violated on 'bottles': bottles >= 0 && bottles <= 2 — rejected write of -1 (key "eng1"); the slot is unchanged
✓ assert fire_warning_on("eng2").ok == true
✓ assert warning_clears("eng2").ok == true
✓ assert suppression_state("eng2") == "monitoring"
17 tests: 17 passed, 0 failed$ soma verify fire_bottle.cell
Verifying fire_bottle.cell...
State machine 'suppression': 5 states, initial 'monitoring'
States: [bottle_one_fired, bottles_exhausted, fire_warning, fuel_shutoff, monitoring]
✓ 5 states, 5 transitions
✓ all states reachable from 'monitoring'
✓ terminal states: [bottles_exhausted]
✓ no deadlocks
✓ liveness: every state can eventually reach a terminal state
⚠ dynamic transition targets: handler `discharge_bottle` uses a dynamic (computed) transition target — the state these handlers move to is computed at run time (from input or an LLM answer), so the machine's safety is checked at run time for them, not proven
✓ termination: all 5 handlers structurally terminate
⚠ refinement (partial — dynamic targets fall back to runtime check): handler `discharge_bottle` calls transition() with a computed target — which edge it takes is checked at run time (invalid_transition), not proven; write the target as a literal state to have it verified
✓ refinement (partial — dynamic targets fall back to runtime check): handler `fire_warning_on` ⟶ {fire_warning}
✓ refinement (partial — dynamic targets fall back to runtime check): handler `warning_clears` ⟶ {monitoring}
✓ refinement (partial — dynamic targets fall back to runtime check): handler `pull_handle` ⟶ {fuel_shutoff}
✓ refinement (partial — dynamic targets fall back to runtime check): handler `discharge_bottle` ⟶ <dynamic>
· transition sources: `fire_warning_on`, `warning_clears`, `pull_handle` 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)
FireSuppression/invariants:
✓ invariant bottles <= 2 — writer 'discharge_bottle' proven by induction (writes left - 1)
✓ invariant bottles >= 0 && bottles <= 2 — writer 'fire_warning_on' proven (writes 2)
⚠ invariant bottles >= 0 && bottles <= 2 — runtime-checked (computed values): discharge_bottle → bottles [bottles >= 0] because `left - 1` is only known to lie in [-1, 1] — narrow it: `require (left - 1) >= 0 else …`
12 passed, 3 warnings, 0 failures
Temporal properties for 'suppression':
✓ deadlock_free — no deadlocks in any reachable state
Temporal: 1 passed, 0 failed
VERIFY OK