nixfleet_state_machine/rollout/transitions/
mod.rs

1//! Rollout-reducer transition dispatch (RFC-0008 §3 + §7).
2//!
3//! Each source-state module holds the (legal-event) match arms; this
4//! file is the single entry point the public `step()` calls. The
5//! reducer enforces transition legality — the applier owns aggregation
6//! (e.g., "all hosts Converged → emit `RolloutTerminal`") so the
7//! reducer can stay pure.
8
9use chrono::{DateTime, Utc};
10
11use crate::rollout::effect::RolloutEffect;
12use crate::rollout::error::RolloutTransitionError;
13use crate::rollout::event::RolloutEvent;
14use crate::rollout::state::{RolloutRecord, RolloutState};
15
16mod active;
17mod converging;
18mod failed;
19mod opening;
20mod pruned;
21mod reverted;
22mod superseded;
23mod terminal;
24
25/// Dispatch a `RolloutEvent` against the current `RolloutRecord`.
26pub(crate) fn dispatch(
27    record: RolloutRecord,
28    event: RolloutEvent,
29    now: DateTime<Utc>,
30) -> Result<(RolloutRecord, Vec<RolloutEffect>), RolloutTransitionError> {
31    match record.state {
32        RolloutState::Opening => opening::step(record, event, now),
33        RolloutState::Active => active::step(record, event, now),
34        RolloutState::Converging => converging::step(record, event, now),
35        RolloutState::Terminal => terminal::step(record, event, now),
36        RolloutState::Reverted => reverted::step(record, event, now),
37        RolloutState::Failed => failed::step(record, event, now),
38        RolloutState::Superseded => superseded::step(record, event, now),
39        RolloutState::Pruned => pruned::step(record, event, now),
40    }
41}
42
43/// Helper: build the [`RolloutEffect::RecordRolloutTransition`] effect
44/// for a state transition. Lives here so the per-state modules don't
45/// re-derive it.
46pub(super) fn transition_effect(
47    record: &RolloutRecord,
48    from: RolloutState,
49    to: RolloutState,
50    at: DateTime<Utc>,
51) -> RolloutEffect {
52    RolloutEffect::RecordRolloutTransition {
53        rollout_id: record.rollout_id.clone(),
54        from,
55        to,
56        at,
57    }
58}
59
60/// Helper: build a `RolloutTransitionError::IllegalForState` for events
61/// that don't apply to the current state.
62pub(super) fn illegal(
63    from: RolloutState,
64    event: &RolloutEvent,
65    rollout_id: crate::rollout::state::RolloutId,
66) -> RolloutTransitionError {
67    RolloutTransitionError::IllegalForState {
68        from,
69        event: event.kind(),
70        rollout_id,
71    }
72}