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 advance(&mut self) {
57 self.current_phase += 1;
58 self.blocked_count = 0;
59 }
60
61 pub fn record_block(&mut self) -> usize {
63 self.blocked_count += 1;
64 self.blocked_count
65 }
66
67 pub fn is_complete(&self) -> bool {
69 match &self.contract {
70 None => true,
71 Some(c) => self.current_phase >= c.phases.len(),
72 }
73 }
74}
75
76impl Default for MilestoneTracker {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use crate::types::milestone::{MilestonePhase, MilestoneRollbackPolicy};
86
87 #[test]
88 fn test_tracker_no_contract_is_complete() {
89 let tracker = MilestoneTracker::new();
90 assert!(tracker.is_complete());
91 assert_eq!(tracker.current_phase_id(), None);
92 assert!(tracker.current_criteria().is_empty());
93 }
94
95 #[test]
96 fn test_tracker_single_phase_is_incomplete_until_passed() {
97 use crate::types::milestone::MilestoneUnlockPolicy;
98 let contract = MilestoneContract {
99 phases: vec![MilestonePhase {
100 id: "phase1".to_string(),
101 criteria: vec!["c1".to_string()],
102 unlocks: vec![],
103 retry_policy: None,
104 verifier: None,
105 required_evidence: vec![],
106 unlock_policy: MilestoneUnlockPolicy::Immediate,
107 rollback_policy: MilestoneRollbackPolicy::Terminate,
108 }],
109 };
110 let mut tracker = MilestoneTracker::new();
111 tracker.load_contract(contract);
112
113 assert!(!tracker.is_complete());
114 assert_eq!(tracker.current_phase_id(), Some("phase1"));
115 assert_eq!(tracker.current_criteria(), &["c1".to_string()]);
116 }
117
118 #[test]
119 fn test_tracker_multi_phase_advances_on_pass() {
120 use crate::types::milestone::MilestoneUnlockPolicy;
121 let contract = MilestoneContract {
122 phases: vec![
123 MilestonePhase {
124 id: "phase1".to_string(),
125 criteria: vec!["c1".to_string()],
126 unlocks: vec![],
127 retry_policy: None,
128 verifier: None,
129 required_evidence: vec![],
130 unlock_policy: MilestoneUnlockPolicy::Immediate,
131 rollback_policy: MilestoneRollbackPolicy::Terminate,
132 },
133 MilestonePhase {
134 id: "phase2".to_string(),
135 criteria: vec!["c2".to_string()],
136 unlocks: vec![],
137 retry_policy: None,
138 verifier: None,
139 required_evidence: vec![],
140 unlock_policy: MilestoneUnlockPolicy::Immediate,
141 rollback_policy: MilestoneRollbackPolicy::Terminate,
142 },
143 ],
144 };
145 let mut tracker = MilestoneTracker::new();
146 tracker.load_contract(contract);
147
148 assert_eq!(tracker.current_phase_id(), Some("phase1"));
149 tracker.advance();
150 assert_eq!(tracker.current_phase_id(), Some("phase2"));
151 tracker.advance();
152 assert!(tracker.is_complete());
153 assert_eq!(tracker.current_phase_id(), None);
154 }
155}