State machine¶
The mission posture FSM is hand rolled (ADR 0004). The transition
table lives in src/nous/state/machine.py and is reviewable in one
screen. The diagram below mirrors that table; if they drift, the
diagram is wrong, not the code.
Badge: filtered. The FSM does not estimate, but its transitions are
audited and its refusal of unknown triggers is a hard error rather
than a silent no-op.
stateDiagram-v2
[*] --> STOWED
STOWED --> BOOT: boot
BOOT --> IDLE: ready
BOOT --> FAULT: fault
BOOT --> SHUTDOWN: shutdown
IDLE --> MISSION: mission
IDLE --> RELAY: relay
IDLE --> MONITORING: monitoring
IDLE --> C2: c2
IDLE --> SHUTDOWN: shutdown
MISSION --> DEGRADED: degrade
MISSION --> THERMAL_LIMIT: thermal_limit
MISSION --> LOW_POWER: low_power
MISSION --> IDLE: complete
MISSION --> FAULT: fault
MISSION --> SHUTDOWN: shutdown
RELAY --> DEGRADED: degrade
RELAY --> IDLE: complete
RELAY --> SHUTDOWN: shutdown
MONITORING --> DEGRADED: degrade
MONITORING --> IDLE: complete
MONITORING --> SHUTDOWN: shutdown
C2 --> DEGRADED: degrade
C2 --> IDLE: complete
C2 --> SHUTDOWN: shutdown
DEGRADED --> MISSION: recover
DEGRADED --> SAFE: safe
DEGRADED --> FAULT: fault
DEGRADED --> SHUTDOWN: shutdown
THERMAL_LIMIT --> MISSION: cool
THERMAL_LIMIT --> SAFE: safe
THERMAL_LIMIT --> SHUTDOWN: shutdown
LOW_POWER --> MISSION: recover
LOW_POWER --> SAFE: safe
LOW_POWER --> SHUTDOWN: shutdown
SAFE --> IDLE: recover
SAFE --> SHUTDOWN: shutdown
FAULT --> STOWED: reset
SHUTDOWN --> STOWED: reset
Trigger surface¶
| Trigger | Effect | Originator (today) |
|---|---|---|
boot |
STOWED -> BOOT |
controller |
ready |
BOOT -> IDLE |
controller |
mission, relay, monitoring, c2 |
IDLE -> <mode> |
controller |
degrade |
<active mode> -> DEGRADED |
controller or, when wired, the state machine itself (BL-022) |
thermal_limit |
MISSION -> THERMAL_LIMIT |
controller (DR-2 will let the FSM raise this itself when the thermal estimator lands) |
low_power |
MISSION -> LOW_POWER |
controller (DR-2, again) |
cool, recover, safe |
exits from degraded postures | controller |
complete |
<active mode> -> IDLE |
controller |
shutdown |
<most modes> -> SHUTDOWN |
controller |
reset |
FAULT -> STOWED, SHUTDOWN -> STOWED |
controller |
fault |
<most modes> -> FAULT |
engine or controller |
Guards and what it does not do today¶
The FSM refuses unsafe transitions when the controller provides the relevant safety context. ADR 0018 wires two guards:
IDLE -> MISSION,DEGRADED -> recover,THERMAL_LIMIT -> coolrefuse whenthermal_headroom_c < thermal_headroom_threshold_c(SC-2 from the STPA artefacts).LOW_POWER -> recoverrefuses whensoc_pct < soc_pct_critical.
Engine.request_transition populates the safety context from live
subsystem state, so a controller calling the FSM through the engine
sees the guards in action. A guard refusal raises GuardDenied and
is recorded on StateMachine.refusals() for the audit log.
The FSM still does not initiate transitions on its own (it raises
thermal_limit and low_power only when a controller or the engine
asks it to). DR-2 tracks that follow-up under BL-022.
The audit log records every transition. The trigger names are stable
across versions; the schema is captured in docs/state-machine.md
(canonical) and in this showcase page (presentation).