deepstrike_core/scheduler/
milestone.rs1use crate::types::milestone::{MilestoneContract, MilestonePhase};
4
5pub struct MilestoneTracker {
8 contract: Option<MilestoneContract>,
10 current_phase: usize,
12 blocked_count: usize,
14}
15
16impl MilestoneTracker {
17 pub fn new() -> Self {
19 Self {
20 contract: None,
21 current_phase: 0,
22 blocked_count: 0,
23 }
24 }
25
26 pub fn load_contract(&mut self, contract: MilestoneContract) {
28 self.contract = Some(contract);
29 self.current_phase = 0;
30 self.blocked_count = 0;
31 }
32
33 pub fn current_phase(&self) -> Option<&MilestonePhase> {
37 self.contract
38 .as_ref()
39 .and_then(|c| c.phases.get(self.current_phase))
40 }
41
42 pub fn current_phase_id(&self) -> Option<&str> {
45 self.current_phase().map(|p| p.id.as_str())
46 }
47
48 pub fn current_criteria(&self) -> &[String] {
50 self.current_phase()
51 .map(|p| p.criteria.as_slice())
52 .unwrap_or(&[])
53 }
54
55 pub fn restore_cursor(&mut self, phase_id: Option<&str>, blocked_count: usize) -> bool {
62 let Some(contract) = &self.contract else {
63 return phase_id.is_none();
64 };
65 let index = match phase_id {
66 None => contract.phases.len(),
67 Some(id) => match contract.phases.iter().position(|phase| phase.id == id) {
68 Some(index) => index,
69 None => return false,
70 },
71 };
72 self.current_phase = index;
73 self.blocked_count = blocked_count;
74 true
75 }
76
77 pub fn blocked_count(&self) -> usize {
80 self.blocked_count
81 }
82
83 pub fn advance(&mut self) {
85 self.current_phase += 1;
86 self.blocked_count = 0;
87 }
88
89 pub fn record_block(&mut self) -> usize {
91 self.blocked_count += 1;
92 self.blocked_count
93 }
94
95 pub fn is_complete(&self) -> bool {
97 match &self.contract {
98 None => true,
99 Some(c) => self.current_phase >= c.phases.len(),
100 }
101 }
102}
103
104impl Default for MilestoneTracker {
105 fn default() -> Self {
106 Self::new()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::types::milestone::{MilestonePhase, MilestoneRollbackPolicy};
114
115 #[test]
116 fn test_tracker_no_contract_is_complete() {
117 let tracker = MilestoneTracker::new();
118 assert!(tracker.is_complete());
119 assert_eq!(tracker.current_phase_id(), None);
120 assert!(tracker.current_criteria().is_empty());
121 }
122
123 #[test]
124 fn test_tracker_single_phase_is_incomplete_until_passed() {
125 use crate::types::milestone::MilestoneUnlockPolicy;
126 let contract = MilestoneContract {
127 phases: vec![MilestonePhase {
128 id: "phase1".to_string(),
129 criteria: vec!["c1".to_string()],
130 unlocks: vec![],
131 retry_policy: None,
132 verifier: None,
133 required_evidence: vec![],
134 unlock_policy: MilestoneUnlockPolicy::Immediate,
135 rollback_policy: MilestoneRollbackPolicy::Terminate,
136 }],
137 };
138 let mut tracker = MilestoneTracker::new();
139 tracker.load_contract(contract);
140
141 assert!(!tracker.is_complete());
142 assert_eq!(tracker.current_phase_id(), Some("phase1"));
143 assert_eq!(tracker.current_criteria(), &["c1".to_string()]);
144 }
145
146 #[test]
147 fn test_tracker_multi_phase_advances_on_pass() {
148 use crate::types::milestone::MilestoneUnlockPolicy;
149 let contract = MilestoneContract {
150 phases: vec![
151 MilestonePhase {
152 id: "phase1".to_string(),
153 criteria: vec!["c1".to_string()],
154 unlocks: vec![],
155 retry_policy: None,
156 verifier: None,
157 required_evidence: vec![],
158 unlock_policy: MilestoneUnlockPolicy::Immediate,
159 rollback_policy: MilestoneRollbackPolicy::Terminate,
160 },
161 MilestonePhase {
162 id: "phase2".to_string(),
163 criteria: vec!["c2".to_string()],
164 unlocks: vec![],
165 retry_policy: None,
166 verifier: None,
167 required_evidence: vec![],
168 unlock_policy: MilestoneUnlockPolicy::Immediate,
169 rollback_policy: MilestoneRollbackPolicy::Terminate,
170 },
171 ],
172 };
173 let mut tracker = MilestoneTracker::new();
174 tracker.load_contract(contract);
175
176 assert_eq!(tracker.current_phase_id(), Some("phase1"));
177 tracker.advance();
178 assert_eq!(tracker.current_phase_id(), Some("phase2"));
179 tracker.advance();
180 assert!(tracker.is_complete());
181 assert_eq!(tracker.current_phase_id(), None);
182 }
183}