soma.cell

Autoclave door — a quick-opening door never unlocks under pressure

Quick-opening pressure vessel doors (autoclaves, sterilizers, curing vessels) have killed operators for a century the same way: residual pressure behind a door someone believed was vented, and the door becomes a projectile. ASME PCC interlocks demand the door cannot unlock until pressure is verifiably zero. Here {pressurized, door open} is not a state: steam is only admitted behind a locked door, and the lock releases only from `sealed_cold` — the exhaust edge zeroes the chamber BEFORE the unlock edge exists. The chamber ledger refuses any pressure beyond the 300 kPa nameplate.

A Soma program from the safety_interlocks part of the verified corpus: 96 lines, cells AutoclaveControl, AutoclaveTests. It passes soma check with soma 3.2.4.

How it is built

The cell AutoclaveControl declares 5 signals in its face: lock_door(id: String) -> String, admit_steam(id: String, kpa: Int) -> Map, exhaust(id: String) -> String, unlock_door(id: String) -> Map and chamber_kpa(id: String) -> Int; keeps 1 memory slot: chamber (Map<String, Int>, persistent and consistent) under the invariant chamber >= 0 && chamber <= 300. Its state machine door starts in open_cold and allows 4 transitions: open_cold -> sealed_cold, sealed_cold -> open_cold, sealed_cold -> pressurized and pressurized -> sealed_cold. Handlers: lock_door (line 35), admit_steam (line 37), exhaust (line 50), unlock_door (line 56) and chamber_kpa (line 65).

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

Generated from soma describe on this file.

The source

autoclave_door.cell

// AUTOCLAVE DOOR — a quick-opening door never unlocks under pressure.
// Quick-opening pressure vessel doors (autoclaves, sterilizers, curing
// vessels) have killed operators for a century the same way: residual
// pressure behind a door someone believed was vented, and the door
// becomes a projectile. ASME PCC interlocks demand the door cannot
// unlock until pressure is verifiably zero. Here {pressurized, door
// open} is not a state: steam is only admitted behind a locked door,
// and the lock releases only from `sealed_cold` — the exhaust edge
// zeroes the chamber BEFORE the unlock edge exists. The chamber ledger
// refuses any pressure beyond the 300 kPa nameplate.

cell AutoclaveControl {
    face {
        signal lock_door(id: String) -> String
        signal admit_steam(id: String, kpa: Int) -> Map
        signal exhaust(id: String) -> String
        signal unlock_door(id: String) -> Map
        signal chamber_kpa(id: String) -> Int
    }

    memory {
        // chamber pressure, kPa gauge; nameplate is 300
        chamber: Map<String, Int> [persistent, consistent]
        invariant chamber >= 0 && chamber <= 300
    }

    state door {
        initial: open_cold
        open_cold -> sealed_cold        // door shut, lugs engaged
        sealed_cold -> open_cold        // unlock: only ever from cold
        sealed_cold -> pressurized      // steam ONLY behind a locked door
        pressurized -> sealed_cold      // exhaust to zero, then the lock frees
    }

    on lock_door(id: String) { transition(id, "sealed_cold")  return get_status(id) }

    on admit_steam(id: String, kpa: Int) {
        let r = try { transition(id, "pressurized") }
        if r.error != () {
            return map("ok", false, "reason", "door not proven locked")
        }
        let w = try { chamber.set(id, kpa) }
        if w.error != () {
            transition(id, "sealed_cold")
            return map("ok", false, "reason", "{kpa} kPa exceeds the 300 kPa nameplate")
        }
        return map("ok", true, "state", get_status(id), "kpa", kpa)
    }

    on exhaust(id: String) {
        chamber.set(id, 0)              // verified zero BEFORE the state moves
        transition(id, "sealed_cold")
        return get_status(id)
    }

    on unlock_door(id: String) {
        let r = try { transition(id, "open_cold") }
        if r.error != () {
            let p = chamber_kpa(id)
            return map("ok", false, "reason", "chamber at {p} kPa — door stays locked")
        }
        return map("ok", true, "state", get_status(id))
    }

    on chamber_kpa(id: String) { return chamber.get(id) ?? 0 }
}

cell test AutoclaveTests {
    rules {
        // steam into an open chamber: the edge does not exist
        assert_fails transition("a1", "pressurized")
        assert admit_steam("a1", 200).ok == false

        // lawful cycle: lock, pressurize
        assert lock_door("a1") == "sealed_cold"
        assert admit_steam("a1", 210).ok == true
        assert chamber_kpa("a1") == 210

        // THE killer: unlocking under pressure. Refused — no such edge.
        assert unlock_door("a1").ok == false
        assert_fails transition("a1", "open_cold")

        // exhaust zeroes the chamber, and only then the lock frees
        assert exhaust("a1") == "sealed_cold"
        assert chamber_kpa("a1") == 0
        assert unlock_door("a1").ok == true

        // the nameplate is a wall, not a warning
        assert_fails chamber.set("a1", 301)
        assert_fails chamber.set("a1", -5)
        assert lock_door("a1") == "sealed_cold"
        assert admit_steam("a1", 999).ok == false
        assert chamber_kpa("a1") == 0
    }
}

What the toolchain says

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

$ soma check autoclave_door.cell
✓ All checks passed.
$ soma test autoclave_door.cell
test AutoclaveTests ...
  ✓ assert_fails transition("a1", "pressurized") — raised invalid transition: open_cold → pressurized. Current state: 'open_cold'. Valid targets: [sealed_cold]
  ✓ assert admit_steam("a1", 200).ok == false
  ✓ assert lock_door("a1") == "sealed_cold"
  ✓ assert admit_steam("a1", 210).ok == true
  ✓ assert chamber_kpa("a1") == 210
  ✓ assert unlock_door("a1").ok == false
  ✓ assert_fails transition("a1", "open_cold") — raised invalid transition: pressurized → open_cold. Current state: 'pressurized'. Valid targets: [sealed_cold]
  ✓ assert exhaust("a1") == "sealed_cold"
  ✓ assert chamber_kpa("a1") == 0
  ✓ assert unlock_door("a1").ok == true
  ✓ assert_fails chamber.set("a1", 301) — raised memory invariant violated on 'chamber': chamber >= 0 && chamber <= 300 — rejected write of 301 (key "a1"); the slot is unchanged
  ✓ assert_fails chamber.set("a1", -5) — raised memory invariant violated on 'chamber': chamber >= 0 && chamber <= 300 — rejected write of -5 (key "a1"); the slot is unchanged
  ✓ assert lock_door("a1") == "sealed_cold"
  ✓ assert admit_steam("a1", 999).ok == false
  ✓ assert chamber_kpa("a1") == 0

15 tests: 15 passed, 0 failed
$ soma verify autoclave_door.cell
Verifying autoclave_door.cell...

State machine 'door': 3 states, initial 'open_cold'
  States: [open_cold, pressurized, sealed_cold]

  ✓ 3 states, 4 transitions
  ✓ all states reachable from 'open_cold'
  ✓ reactive machine: no terminal state, and every reachable state can return to 'open_cold'
  ✓ no deadlocks
  ✓ liveness: every reachable state can return to 'open_cold' (reactive machine)
  ✓ think-isolated: CTL safety properties hold regardless of LLM output (4 handlers, 5 literal transitions, 0 dynamic)
  ✓ termination: all 5 handlers structurally terminate
  ✓ refinement: handler `lock_door` ⟶ {sealed_cold}
  ✓ refinement: handler `admit_steam` ⟶ {pressurized, sealed_cold [if w.error != ()]}
  ✓ refinement: handler `exhaust` ⟶ {sealed_cold}
  ✓ refinement: handler `unlock_door` ⟶ {open_cold}
  · transition sources: `lock_door`, `admit_steam`, `exhaust`, `unlock_door` 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)

AutoclaveControl/invariants:

  ✓ invariant chamber >= 0 && chamber <= 300 — writer 'exhaust' proven (writes 0)
  ⚠ invariant chamber >= 0 && chamber <= 300 — runtime-checked (computed values): admit_steam → chamber because `kpa` is a parameter (narrow it: `require kpa >= 0 && kpa <= 300 else …`)

12 passed, 1 warnings, 0 failures

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

Builtins used

get_status, map, transition

Source file: /corpus/safety_interlocks/autoclave_door.cell · examples/corpus/safety_interlocks/autoclave_door.cell