deepstrike_core/scheduler/
milestone.rs1use crate::types::milestone::MilestoneContract;
8
9pub struct MilestoneTracker {
15 pub(crate) contract: Option<MilestoneContract>,
17 pub(crate) current_phase: usize,
19 pub(crate) blocked_count: usize,
21}
22
23impl MilestoneTracker {
24 pub fn new() -> Self {
26 Self {
27 contract: None,
28 current_phase: 0,
29 blocked_count: 0,
30 }
31 }
32
33 pub fn load_contract(&mut self, contract: MilestoneContract) {
35 self.contract = Some(contract);
36 self.current_phase = 0;
37 self.blocked_count = 0;
38 }
39
40 pub fn current_phase_id(&self) -> Option<&str> {
43 self.contract
44 .as_ref()
45 .and_then(|c| c.phases.get(self.current_phase))
46 .map(|p| p.id.as_str())
47 }
48
49 pub fn current_criteria(&self) -> &[String] {
51 self.contract
52 .as_ref()
53 .and_then(|c| c.phases.get(self.current_phase))
54 .map(|p| p.criteria.as_slice())
55 .unwrap_or(&[])
56 }
57
58 pub fn is_complete(&self) -> bool {
60 match &self.contract {
61 None => true,
62 Some(c) => self.current_phase >= c.phases.len(),
63 }
64 }
65}
66
67impl Default for MilestoneTracker {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76 use crate::types::milestone::{MilestonePhase, MilestoneRollbackPolicy};
77
78 #[test]
79 fn test_tracker_no_contract_is_complete() {
80 let tracker = MilestoneTracker::new();
81 assert!(tracker.is_complete());
82 assert_eq!(tracker.current_phase_id(), None);
83 assert!(tracker.current_criteria().is_empty());
84 }
85
86 #[test]
87 fn test_tracker_single_phase_is_incomplete_until_passed() {
88 use crate::types::milestone::MilestoneUnlockPolicy;
89 let contract = MilestoneContract {
90 phases: vec![MilestonePhase {
91 id: "phase1".to_string(),
92 criteria: vec!["c1".to_string()],
93 unlocks: vec![],
94 retry_policy: None,
95 verifier: None,
96 required_evidence: vec![],
97 unlock_policy: MilestoneUnlockPolicy::Immediate,
98 rollback_policy: MilestoneRollbackPolicy::Terminate,
99 }],
100 };
101 let mut tracker = MilestoneTracker::new();
102 tracker.load_contract(contract);
103
104 assert!(!tracker.is_complete());
105 assert_eq!(tracker.current_phase_id(), Some("phase1"));
106 assert_eq!(tracker.current_criteria(), &["c1".to_string()]);
107 }
108
109 #[test]
110 fn test_tracker_multi_phase_advances_on_pass() {
111 use crate::types::milestone::MilestoneUnlockPolicy;
112 let contract = MilestoneContract {
113 phases: vec![
114 MilestonePhase {
115 id: "phase1".to_string(),
116 criteria: vec!["c1".to_string()],
117 unlocks: vec![],
118 retry_policy: None,
119 verifier: None,
120 required_evidence: vec![],
121 unlock_policy: MilestoneUnlockPolicy::Immediate,
122 rollback_policy: MilestoneRollbackPolicy::Terminate,
123 },
124 MilestonePhase {
125 id: "phase2".to_string(),
126 criteria: vec!["c2".to_string()],
127 unlocks: vec![],
128 retry_policy: None,
129 verifier: None,
130 required_evidence: vec![],
131 unlock_policy: MilestoneUnlockPolicy::Immediate,
132 rollback_policy: MilestoneRollbackPolicy::Terminate,
133 },
134 ],
135 };
136 let mut tracker = MilestoneTracker::new();
137 tracker.load_contract(contract);
138
139 assert_eq!(tracker.current_phase_id(), Some("phase1"));
140 tracker.current_phase += 1; assert_eq!(tracker.current_phase_id(), Some("phase2"));
142 tracker.current_phase += 1;
143 assert!(tracker.is_complete());
144 assert_eq!(tracker.current_phase_id(), None);
145 }
146}