nixfleet_state_machine/rollout/transitions/
failed.rs1use chrono::{DateTime, Utc};
5
6use super::{illegal, transition_effect};
7use crate::rollout::effect::RolloutEffect;
8use crate::rollout::error::RolloutTransitionError;
9use crate::rollout::event::RolloutEvent;
10use crate::rollout::state::{RolloutRecord, RolloutState};
11
12pub(super) fn step(
13 mut record: RolloutRecord,
14 event: RolloutEvent,
15 _now: DateTime<Utc>,
16) -> Result<(RolloutRecord, Vec<RolloutEffect>), RolloutTransitionError> {
17 match event {
18 RolloutEvent::SuccessorOpened { at, .. } => {
19 record.state = RolloutState::Superseded;
20 record.superseded_at = Some(at);
21 let effects = vec![transition_effect(
22 &record,
23 RolloutState::Failed,
24 RolloutState::Superseded,
25 at,
26 )];
27 Ok((record, effects))
28 }
29 RolloutEvent::RetentionExpired { at, .. } => {
30 record.state = RolloutState::Pruned;
31 let effects = vec![transition_effect(
32 &record,
33 RolloutState::Failed,
34 RolloutState::Pruned,
35 at,
36 )];
37 Ok((record, effects))
38 }
39 RolloutEvent::OperatorClearance { .. } => Err(RolloutTransitionError::Unimplemented {
40 from: RolloutState::Failed,
41 event: event.kind(),
42 }),
43 RolloutEvent::HostStateChanged { .. }
44 | RolloutEvent::HostJoined { .. }
45 | RolloutEvent::WaveAdvanced { .. }
46 | RolloutEvent::RolloutTerminal { .. } => Ok((record, Vec::new())),
47 RolloutEvent::RolloutOpened { .. } => Err(illegal(
48 RolloutState::Failed,
49 &event,
50 record.rollout_id.clone(),
51 )),
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58 use chrono::TimeZone;
59
60 fn t0() -> DateTime<Utc> {
61 Utc.with_ymd_and_hms(2026, 5, 16, 1, 0, 0).unwrap()
62 }
63
64 fn failed_record() -> RolloutRecord {
65 RolloutRecord {
66 rollout_id: "r1".into(),
67 channel: "stable".into(),
68 target_ref: "ref-1".into(),
69 state: RolloutState::Failed,
70 current_wave: 0,
71 opened_event_log_seq: None,
72 last_transition_event_log_seq: None,
73 opened_at: t0(),
74 terminal_at: None,
75 superseded_at: None,
76 }
77 }
78
79 #[test]
80 fn successor_opened_transitions_to_superseded() {
81 let event = RolloutEvent::SuccessorOpened {
82 superseded_rollout_id: "r1".into(),
83 successor_rollout_id: "r2".into(),
84 at: t0(),
85 };
86 let (record, _) = step(failed_record(), event, t0()).unwrap();
87 assert_eq!(record.state, RolloutState::Superseded);
88 }
89
90 #[test]
91 fn retention_expired_transitions_to_pruned() {
92 let event = RolloutEvent::RetentionExpired {
93 rollout_id: "r1".into(),
94 at: t0(),
95 };
96 let (record, _) = step(failed_record(), event, t0()).unwrap();
97 assert_eq!(record.state, RolloutState::Pruned);
98 }
99}