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