1use crate::{AgentId, Dna};
7use serde::{Deserialize, Serialize};
8use std::cmp::Ordering;
9use std::collections::{HashMap, HashSet};
10use std::fmt;
11use std::sync::{Arc, RwLock};
12use thiserror::Error;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18pub struct Version {
19 pub major: u32,
20 pub minor: u32,
21 pub patch: u32,
22}
23
24impl Version {
25 pub fn new(major: u32, minor: u32, patch: u32) -> Self {
27 Self {
28 major,
29 minor,
30 patch,
31 }
32 }
33
34 pub fn is_compatible_with(&self, other: &Version) -> bool {
37 self.major == other.major && self.minor >= other.minor
38 }
39
40 pub fn is_breaking_change(&self, other: &Version) -> bool {
42 self.major != other.major
43 }
44
45 pub fn is_minor_update(&self, other: &Version) -> bool {
47 self.major == other.major && self.minor != other.minor
48 }
49
50 pub fn is_patch_update(&self, other: &Version) -> bool {
52 self.major == other.major && self.minor == other.minor && self.patch != other.patch
53 }
54
55 pub fn next_major(&self) -> Self {
57 Self::new(self.major + 1, 0, 0)
58 }
59
60 pub fn next_minor(&self) -> Self {
62 Self::new(self.major, self.minor + 1, 0)
63 }
64
65 pub fn next_patch(&self) -> Self {
67 Self::new(self.major, self.minor, self.patch + 1)
68 }
69}
70
71impl fmt::Display for Version {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
74 }
75}
76
77impl PartialOrd for Version {
78 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
79 Some(self.cmp(other))
80 }
81}
82
83impl Ord for Version {
84 fn cmp(&self, other: &Self) -> Ordering {
85 match self.major.cmp(&other.major) {
86 Ordering::Equal => match self.minor.cmp(&other.minor) {
87 Ordering::Equal => self.patch.cmp(&other.patch),
88 other => other,
89 },
90 other => other,
91 }
92 }
93}
94
95#[derive(Debug, Error)]
97pub enum VersionError {
98 #[error("Incompatible version: {0} is not compatible with {1}")]
99 IncompatibleVersion(Version, Version),
100 #[error("Version not found: {0}")]
101 VersionNotFound(Version),
102 #[error("Agent version not found: {0}")]
103 AgentVersionNotFound(AgentId),
104 #[error("Invalid version string: {0}")]
105 InvalidVersionString(String),
106 #[error("Deployment error: {0}")]
107 DeploymentError(String),
108 #[error("Migration error: {0}")]
109 MigrationError(String),
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct VersionMetadata {
115 pub version: Version,
116 pub dna: Dna,
117 pub description: String,
118 pub changelog: Vec<String>,
119 pub deprecated: bool,
120 pub migration_path: Option<Vec<Version>>, }
122
123impl VersionMetadata {
124 pub fn new(version: Version, dna: Dna, description: String) -> Self {
125 Self {
126 version,
127 dna,
128 description,
129 changelog: Vec::new(),
130 deprecated: false,
131 migration_path: None,
132 }
133 }
134
135 pub fn with_changelog(mut self, changelog: Vec<String>) -> Self {
136 self.changelog = changelog;
137 self
138 }
139
140 pub fn with_migration_path(mut self, path: Vec<Version>) -> Self {
141 self.migration_path = Some(path);
142 self
143 }
144
145 pub fn deprecate(mut self) -> Self {
146 self.deprecated = true;
147 self
148 }
149}
150
151pub struct VersionRegistry {
153 versions: Arc<RwLock<HashMap<Version, VersionMetadata>>>,
154 agent_versions: Arc<RwLock<HashMap<AgentId, Version>>>,
155}
156
157impl VersionRegistry {
158 pub fn new() -> Self {
159 Self {
160 versions: Arc::new(RwLock::new(HashMap::new())),
161 agent_versions: Arc::new(RwLock::new(HashMap::new())),
162 }
163 }
164
165 pub fn register_version(&self, metadata: VersionMetadata) -> Result<(), VersionError> {
167 let mut versions = self.versions.write().expect("Lock poisoned: versions");
168 versions.insert(metadata.version, metadata);
169 Ok(())
170 }
171
172 pub fn get_version(&self, version: &Version) -> Result<VersionMetadata, VersionError> {
174 let versions = self.versions.read().expect("Lock poisoned: versions");
175 versions
176 .get(version)
177 .cloned()
178 .ok_or(VersionError::VersionNotFound(*version))
179 }
180
181 pub fn list_versions(&self) -> Vec<Version> {
183 let versions = self.versions.read().expect("Lock poisoned: versions");
184 let mut result: Vec<Version> = versions.keys().copied().collect();
185 result.sort();
186 result
187 }
188
189 pub fn latest_version(&self) -> Option<Version> {
191 let versions = self.versions.read().expect("Lock poisoned: versions");
192 versions.keys().max().copied()
193 }
194
195 pub fn register_agent(&self, agent_id: AgentId, version: Version) -> Result<(), VersionError> {
197 self.get_version(&version)?;
199
200 let mut agent_versions = self
201 .agent_versions
202 .write()
203 .expect("Lock poisoned: agent_versions");
204 agent_versions.insert(agent_id, version);
205 Ok(())
206 }
207
208 pub fn get_agent_version(&self, agent_id: &AgentId) -> Result<Version, VersionError> {
210 let agent_versions = self
211 .agent_versions
212 .read()
213 .expect("Lock poisoned: agent_versions");
214 agent_versions
215 .get(agent_id)
216 .copied()
217 .ok_or(VersionError::AgentVersionNotFound(*agent_id))
218 }
219
220 pub fn update_agent_version(
222 &self,
223 agent_id: AgentId,
224 new_version: Version,
225 ) -> Result<Version, VersionError> {
226 self.get_version(&new_version)?;
228
229 let mut agent_versions = self
230 .agent_versions
231 .write()
232 .expect("Lock poisoned: agent_versions");
233 let old_version = agent_versions
234 .get(&agent_id)
235 .copied()
236 .ok_or(VersionError::AgentVersionNotFound(agent_id))?;
237
238 agent_versions.insert(agent_id, new_version);
239 Ok(old_version)
240 }
241
242 pub fn get_agents_by_version(&self, version: &Version) -> Vec<AgentId> {
244 let agent_versions = self
245 .agent_versions
246 .read()
247 .expect("Lock poisoned: agent_versions");
248 agent_versions
249 .iter()
250 .filter(|(_, v)| *v == version)
251 .map(|(id, _)| *id)
252 .collect()
253 }
254
255 pub fn can_migrate(&self, from: &Version, to: &Version) -> Result<bool, VersionError> {
257 let _from_meta = self.get_version(from)?;
258 let to_meta = self.get_version(to)?;
259
260 if to_meta.deprecated {
262 return Ok(false);
263 }
264
265 if to.is_compatible_with(from) {
267 return Ok(true);
268 }
269
270 if let Some(path) = &to_meta.migration_path {
272 Ok(path.contains(from))
273 } else {
274 Ok(false)
275 }
276 }
277}
278
279impl Default for VersionRegistry {
280 fn default() -> Self {
281 Self::new()
282 }
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub enum RollingUpdateStrategy {
288 Immediate,
290 Batched {
292 batch_size: usize,
293 delay_between_batches_ms: u64,
294 },
295 Sequential { delay_between_agents_ms: u64 },
297 Percentage {
299 percentage: f32,
300 delay_between_batches_ms: u64,
301 },
302}
303
304#[derive(Debug, Clone)]
306pub struct RollingUpdateConfig {
307 pub from_version: Version,
308 pub to_version: Version,
309 pub strategy: RollingUpdateStrategy,
310 pub health_check_required: bool,
311 pub rollback_on_failure: bool,
312 pub max_failures: usize,
313}
314
315impl RollingUpdateConfig {
316 pub fn new(from_version: Version, to_version: Version) -> Self {
317 Self {
318 from_version,
319 to_version,
320 strategy: RollingUpdateStrategy::Batched {
321 batch_size: 10,
322 delay_between_batches_ms: 1000,
323 },
324 health_check_required: true,
325 rollback_on_failure: true,
326 max_failures: 0,
327 }
328 }
329
330 pub fn with_strategy(mut self, strategy: RollingUpdateStrategy) -> Self {
331 self.strategy = strategy;
332 self
333 }
334
335 pub fn with_health_check(mut self, required: bool) -> Self {
336 self.health_check_required = required;
337 self
338 }
339
340 pub fn with_rollback(mut self, enabled: bool) -> Self {
341 self.rollback_on_failure = enabled;
342 self
343 }
344
345 pub fn with_max_failures(mut self, max: usize) -> Self {
346 self.max_failures = max;
347 self
348 }
349}
350
351#[derive(Debug, Clone)]
353pub struct ABTestConfig {
354 pub version_a: Version,
355 pub version_b: Version,
356 pub traffic_split: f32, pub duration_seconds: Option<u64>,
358 pub metrics: Vec<String>, }
360
361impl ABTestConfig {
362 pub fn new(version_a: Version, version_b: Version, traffic_split: f32) -> Self {
363 Self {
364 version_a,
365 version_b,
366 traffic_split: traffic_split.clamp(0.0, 1.0),
367 duration_seconds: None,
368 metrics: Vec::new(),
369 }
370 }
371
372 pub fn with_duration(mut self, seconds: u64) -> Self {
373 self.duration_seconds = Some(seconds);
374 self
375 }
376
377 pub fn with_metrics(mut self, metrics: Vec<String>) -> Self {
378 self.metrics = metrics;
379 self
380 }
381}
382
383#[derive(Debug, Clone)]
385pub struct CanaryConfig {
386 pub stable_version: Version,
387 pub canary_version: Version,
388 pub canary_percentage: f32, pub increment_percentage: f32, pub increment_interval_seconds: u64,
391 pub success_threshold: f32, pub max_failures: usize,
393}
394
395impl CanaryConfig {
396 pub fn new(stable_version: Version, canary_version: Version) -> Self {
397 Self {
398 stable_version,
399 canary_version,
400 canary_percentage: 0.05, increment_percentage: 0.05,
402 increment_interval_seconds: 300, success_threshold: 0.95, max_failures: 3,
405 }
406 }
407
408 pub fn with_initial_percentage(mut self, percentage: f32) -> Self {
409 self.canary_percentage = percentage.clamp(0.0, 1.0);
410 self
411 }
412
413 pub fn with_increment(mut self, percentage: f32, interval_seconds: u64) -> Self {
414 self.increment_percentage = percentage.clamp(0.0, 1.0);
415 self.increment_interval_seconds = interval_seconds;
416 self
417 }
418
419 pub fn with_success_threshold(mut self, threshold: f32) -> Self {
420 self.success_threshold = threshold.clamp(0.0, 1.0);
421 self
422 }
423
424 pub fn with_max_failures(mut self, max: usize) -> Self {
425 self.max_failures = max;
426 self
427 }
428}
429
430pub struct VersionDeployer {
432 registry: Arc<VersionRegistry>,
433}
434
435impl VersionDeployer {
436 pub fn new(registry: Arc<VersionRegistry>) -> Self {
437 Self { registry }
438 }
439
440 pub async fn rolling_update(
442 &self,
443 config: RollingUpdateConfig,
444 agents: Vec<AgentId>,
445 ) -> Result<Vec<AgentId>, VersionError> {
446 if !self
448 .registry
449 .can_migrate(&config.from_version, &config.to_version)?
450 {
451 return Err(VersionError::IncompatibleVersion(
452 config.from_version,
453 config.to_version,
454 ));
455 }
456
457 let mut updated_agents = Vec::new();
458 let mut failed_count = 0;
459
460 let target_agents: Vec<AgentId> = agents
462 .into_iter()
463 .filter(|id| {
464 self.registry
465 .get_agent_version(id)
466 .map(|v| v == config.from_version)
467 .unwrap_or(false)
468 })
469 .collect();
470
471 match config.strategy {
472 RollingUpdateStrategy::Immediate => {
473 for agent_id in target_agents {
474 match self.update_agent(agent_id, config.to_version).await {
475 Ok(_) => updated_agents.push(agent_id),
476 Err(_) => {
477 failed_count += 1;
478 if failed_count > config.max_failures {
479 if config.rollback_on_failure {
480 self.rollback_updates(&updated_agents, config.from_version)
481 .await?;
482 }
483 return Err(VersionError::DeploymentError(
484 "Too many failures during rolling update".to_string(),
485 ));
486 }
487 }
488 }
489 }
490 }
491 RollingUpdateStrategy::Batched {
492 batch_size,
493 delay_between_batches_ms,
494 } => {
495 for batch in target_agents.chunks(batch_size) {
496 for &agent_id in batch {
497 match self.update_agent(agent_id, config.to_version).await {
498 Ok(_) => updated_agents.push(agent_id),
499 Err(_) => {
500 failed_count += 1;
501 if failed_count > config.max_failures {
502 if config.rollback_on_failure {
503 self.rollback_updates(&updated_agents, config.from_version)
504 .await?;
505 }
506 return Err(VersionError::DeploymentError(
507 "Too many failures during rolling update".to_string(),
508 ));
509 }
510 }
511 }
512 }
513 tokio::time::sleep(tokio::time::Duration::from_millis(
514 delay_between_batches_ms,
515 ))
516 .await;
517 }
518 }
519 RollingUpdateStrategy::Sequential {
520 delay_between_agents_ms,
521 } => {
522 for agent_id in target_agents {
523 match self.update_agent(agent_id, config.to_version).await {
524 Ok(_) => updated_agents.push(agent_id),
525 Err(_) => {
526 failed_count += 1;
527 if failed_count > config.max_failures {
528 if config.rollback_on_failure {
529 self.rollback_updates(&updated_agents, config.from_version)
530 .await?;
531 }
532 return Err(VersionError::DeploymentError(
533 "Too many failures during rolling update".to_string(),
534 ));
535 }
536 }
537 }
538 tokio::time::sleep(tokio::time::Duration::from_millis(delay_between_agents_ms))
539 .await;
540 }
541 }
542 RollingUpdateStrategy::Percentage {
543 percentage,
544 delay_between_batches_ms,
545 } => {
546 let batch_size = ((target_agents.len() as f32 * percentage) as usize).max(1);
547 for batch in target_agents.chunks(batch_size) {
548 for &agent_id in batch {
549 match self.update_agent(agent_id, config.to_version).await {
550 Ok(_) => updated_agents.push(agent_id),
551 Err(_) => {
552 failed_count += 1;
553 if failed_count > config.max_failures {
554 if config.rollback_on_failure {
555 self.rollback_updates(&updated_agents, config.from_version)
556 .await?;
557 }
558 return Err(VersionError::DeploymentError(
559 "Too many failures during rolling update".to_string(),
560 ));
561 }
562 }
563 }
564 }
565 tokio::time::sleep(tokio::time::Duration::from_millis(
566 delay_between_batches_ms,
567 ))
568 .await;
569 }
570 }
571 }
572
573 Ok(updated_agents)
574 }
575
576 async fn rollback_updates(
578 &self,
579 agents: &[AgentId],
580 version: Version,
581 ) -> Result<(), VersionError> {
582 for &agent_id in agents {
583 self.update_agent(agent_id, version).await?;
584 }
585 Ok(())
586 }
587
588 async fn update_agent(
590 &self,
591 agent_id: AgentId,
592 new_version: Version,
593 ) -> Result<(), VersionError> {
594 self.registry
595 .update_agent_version(agent_id, new_version)
596 .map(|_| ())
597 }
598
599 pub fn start_ab_test(&self, config: ABTestConfig) -> Result<ABTestDeployment, VersionError> {
601 self.registry.get_version(&config.version_a)?;
603 self.registry.get_version(&config.version_b)?;
604
605 Ok(ABTestDeployment {
606 config,
607 version_a_agents: HashSet::new(),
608 version_b_agents: HashSet::new(),
609 })
610 }
611
612 pub fn start_canary(&self, config: CanaryConfig) -> Result<CanaryDeployment, VersionError> {
614 self.registry.get_version(&config.stable_version)?;
616 self.registry.get_version(&config.canary_version)?;
617
618 Ok(CanaryDeployment {
619 config,
620 canary_agents: HashSet::new(),
621 stable_agents: HashSet::new(),
622 current_percentage: 0.0,
623 success_count: 0,
624 failure_count: 0,
625 })
626 }
627}
628
629pub struct ABTestDeployment {
631 pub config: ABTestConfig,
632 pub version_a_agents: HashSet<AgentId>,
633 pub version_b_agents: HashSet<AgentId>,
634}
635
636impl ABTestDeployment {
637 pub fn assign_agent(&mut self, agent_id: AgentId) -> Version {
639 use rand::Rng;
640 let mut rng = rand::rng();
641
642 if rng.random::<f32>() < self.config.traffic_split {
643 self.version_b_agents.insert(agent_id);
644 self.config.version_b
645 } else {
646 self.version_a_agents.insert(agent_id);
647 self.config.version_a
648 }
649 }
650
651 pub fn stats(&self) -> ABTestStats {
653 ABTestStats {
654 version_a_count: self.version_a_agents.len(),
655 version_b_count: self.version_b_agents.len(),
656 actual_split: if self.version_a_agents.is_empty() && self.version_b_agents.is_empty() {
657 0.0
658 } else {
659 self.version_b_agents.len() as f32
660 / (self.version_a_agents.len() + self.version_b_agents.len()) as f32
661 },
662 }
663 }
664}
665
666#[derive(Debug, Clone)]
668pub struct ABTestStats {
669 pub version_a_count: usize,
670 pub version_b_count: usize,
671 pub actual_split: f32,
672}
673
674pub struct CanaryDeployment {
676 pub config: CanaryConfig,
677 pub canary_agents: HashSet<AgentId>,
678 pub stable_agents: HashSet<AgentId>,
679 pub current_percentage: f32,
680 pub success_count: usize,
681 pub failure_count: usize,
682}
683
684impl CanaryDeployment {
685 pub fn assign_agent(&mut self, agent_id: AgentId) -> Version {
687 use rand::Rng;
688 let mut rng = rand::rng();
689
690 if rng.random::<f32>() < self.current_percentage {
691 self.canary_agents.insert(agent_id);
692 self.config.canary_version
693 } else {
694 self.stable_agents.insert(agent_id);
695 self.config.stable_version
696 }
697 }
698
699 pub fn record_success(&mut self) {
701 self.success_count += 1;
702 }
703
704 pub fn record_failure(&mut self) {
706 self.failure_count += 1;
707 }
708
709 pub fn should_promote(&self) -> bool {
711 if self.success_count + self.failure_count == 0 {
712 return false;
713 }
714
715 let success_rate =
716 self.success_count as f32 / (self.success_count + self.failure_count) as f32;
717 success_rate >= self.config.success_threshold
718 && self.failure_count < self.config.max_failures
719 }
720
721 pub fn should_abort(&self) -> bool {
723 self.failure_count >= self.config.max_failures
724 }
725
726 pub fn increment(&mut self) {
728 self.current_percentage =
729 (self.current_percentage + self.config.increment_percentage).min(1.0);
730 }
731
732 pub fn stats(&self) -> CanaryStats {
734 CanaryStats {
735 canary_count: self.canary_agents.len(),
736 stable_count: self.stable_agents.len(),
737 current_percentage: self.current_percentage,
738 success_count: self.success_count,
739 failure_count: self.failure_count,
740 success_rate: if self.success_count + self.failure_count == 0 {
741 0.0
742 } else {
743 self.success_count as f32 / (self.success_count + self.failure_count) as f32
744 },
745 }
746 }
747}
748
749#[derive(Debug, Clone)]
751pub struct CanaryStats {
752 pub canary_count: usize,
753 pub stable_count: usize,
754 pub current_percentage: f32,
755 pub success_count: usize,
756 pub failure_count: usize,
757 pub success_rate: f32,
758}
759
760#[cfg(test)]
761mod tests {
762 use super::*;
763
764 #[test]
765 fn test_version_ordering() {
766 let v1_0_0 = Version::new(1, 0, 0);
767 let v1_0_1 = Version::new(1, 0, 1);
768 let v1_1_0 = Version::new(1, 1, 0);
769 let v2_0_0 = Version::new(2, 0, 0);
770
771 assert!(v1_0_0 < v1_0_1);
772 assert!(v1_0_1 < v1_1_0);
773 assert!(v1_1_0 < v2_0_0);
774 }
775
776 #[test]
777 fn test_version_compatibility() {
778 let v1_0_0 = Version::new(1, 0, 0);
779 let v1_1_0 = Version::new(1, 1, 0);
780 let v2_0_0 = Version::new(2, 0, 0);
781
782 assert!(v1_1_0.is_compatible_with(&v1_0_0));
783 assert!(!v1_0_0.is_compatible_with(&v1_1_0));
784 assert!(!v2_0_0.is_compatible_with(&v1_0_0));
785 }
786
787 #[test]
788 fn test_version_change_detection() {
789 let v1_0_0 = Version::new(1, 0, 0);
790 let v1_0_1 = Version::new(1, 0, 1);
791 let v1_1_0 = Version::new(1, 1, 0);
792 let v2_0_0 = Version::new(2, 0, 0);
793
794 assert!(v1_0_1.is_patch_update(&v1_0_0));
795 assert!(v1_1_0.is_minor_update(&v1_0_0));
796 assert!(v2_0_0.is_breaking_change(&v1_0_0));
797 }
798
799 #[test]
800 fn test_version_registry() {
801 let registry = VersionRegistry::new();
802 let v1_0_0 = Version::new(1, 0, 0);
803 let dna = Dna::new(vec![1, 2, 3, 4]);
804
805 let metadata = VersionMetadata::new(v1_0_0, dna, "Initial release".to_string());
806 registry.register_version(metadata).unwrap();
807
808 let retrieved = registry.get_version(&v1_0_0).unwrap();
809 assert_eq!(retrieved.version, v1_0_0);
810 }
811
812 #[test]
813 fn test_agent_version_tracking() {
814 let registry = VersionRegistry::new();
815 let v1_0_0 = Version::new(1, 0, 0);
816 let dna = Dna::new(vec![1, 2, 3, 4]);
817 let agent_id = AgentId::new_v4();
818
819 let metadata = VersionMetadata::new(v1_0_0, dna, "Initial release".to_string());
820 registry.register_version(metadata).unwrap();
821 registry.register_agent(agent_id, v1_0_0).unwrap();
822
823 let version = registry.get_agent_version(&agent_id).unwrap();
824 assert_eq!(version, v1_0_0);
825 }
826
827 #[test]
828 fn test_version_update() {
829 let registry = VersionRegistry::new();
830 let v1_0_0 = Version::new(1, 0, 0);
831 let v1_1_0 = Version::new(1, 1, 0);
832 let dna = Dna::new(vec![1, 2, 3, 4]);
833 let agent_id = AgentId::new_v4();
834
835 registry
836 .register_version(VersionMetadata::new(
837 v1_0_0,
838 dna.clone(),
839 "v1.0.0".to_string(),
840 ))
841 .unwrap();
842 registry
843 .register_version(VersionMetadata::new(v1_1_0, dna, "v1.1.0".to_string()))
844 .unwrap();
845 registry.register_agent(agent_id, v1_0_0).unwrap();
846
847 let old_version = registry.update_agent_version(agent_id, v1_1_0).unwrap();
848 assert_eq!(old_version, v1_0_0);
849
850 let new_version = registry.get_agent_version(&agent_id).unwrap();
851 assert_eq!(new_version, v1_1_0);
852 }
853
854 #[test]
855 fn test_get_agents_by_version() {
856 let registry = VersionRegistry::new();
857 let v1_0_0 = Version::new(1, 0, 0);
858 let dna = Dna::new(vec![1, 2, 3, 4]);
859
860 registry
861 .register_version(VersionMetadata::new(v1_0_0, dna, "v1.0.0".to_string()))
862 .unwrap();
863
864 let agent1 = AgentId::new_v4();
865 let agent2 = AgentId::new_v4();
866 registry.register_agent(agent1, v1_0_0).unwrap();
867 registry.register_agent(agent2, v1_0_0).unwrap();
868
869 let agents = registry.get_agents_by_version(&v1_0_0);
870 assert_eq!(agents.len(), 2);
871 assert!(agents.contains(&agent1));
872 assert!(agents.contains(&agent2));
873 }
874
875 #[test]
876 fn test_migration_compatibility() {
877 let registry = VersionRegistry::new();
878 let v1_0_0 = Version::new(1, 0, 0);
879 let v1_1_0 = Version::new(1, 1, 0);
880 let v2_0_0 = Version::new(2, 0, 0);
881 let dna = Dna::new(vec![1, 2, 3, 4]);
882
883 registry
884 .register_version(VersionMetadata::new(
885 v1_0_0,
886 dna.clone(),
887 "v1.0.0".to_string(),
888 ))
889 .unwrap();
890 registry
891 .register_version(VersionMetadata::new(
892 v1_1_0,
893 dna.clone(),
894 "v1.1.0".to_string(),
895 ))
896 .unwrap();
897 registry
898 .register_version(
899 VersionMetadata::new(v2_0_0, dna, "v2.0.0".to_string())
900 .with_migration_path(vec![v1_1_0]),
901 )
902 .unwrap();
903
904 assert!(registry.can_migrate(&v1_0_0, &v1_1_0).unwrap());
905 assert!(!registry.can_migrate(&v1_0_0, &v2_0_0).unwrap());
906 assert!(registry.can_migrate(&v1_1_0, &v2_0_0).unwrap());
907 }
908
909 #[test]
910 fn test_ab_test_assignment() {
911 let mut deployment = ABTestDeployment {
912 config: ABTestConfig::new(Version::new(1, 0, 0), Version::new(1, 1, 0), 0.5),
913 version_a_agents: HashSet::new(),
914 version_b_agents: HashSet::new(),
915 };
916
917 for _ in 0..100 {
919 let agent_id = AgentId::new_v4();
920 deployment.assign_agent(agent_id);
921 }
922
923 let stats = deployment.stats();
924 assert!(stats.version_a_count > 30 && stats.version_a_count < 70);
927 assert!(stats.version_b_count > 30 && stats.version_b_count < 70);
928 }
929
930 #[test]
931 fn test_canary_deployment() {
932 let mut deployment = CanaryDeployment {
933 config: CanaryConfig::new(Version::new(1, 0, 0), Version::new(1, 1, 0))
934 .with_initial_percentage(0.1)
935 .with_max_failures(10), canary_agents: HashSet::new(),
937 stable_agents: HashSet::new(),
938 current_percentage: 0.1,
939 success_count: 0,
940 failure_count: 0,
941 };
942
943 for _ in 0..95 {
945 deployment.record_success();
946 }
947 for _ in 0..5 {
948 deployment.record_failure();
949 }
950
951 assert!(deployment.should_promote());
952 assert!(!deployment.should_abort());
953
954 let stats = deployment.stats();
955 assert_eq!(stats.success_rate, 0.95);
956 }
957
958 #[test]
959 fn test_canary_abort() {
960 let mut deployment = CanaryDeployment {
961 config: CanaryConfig::new(Version::new(1, 0, 0), Version::new(1, 1, 0))
962 .with_max_failures(5),
963 canary_agents: HashSet::new(),
964 stable_agents: HashSet::new(),
965 current_percentage: 0.1,
966 success_count: 0,
967 failure_count: 0,
968 };
969
970 for _ in 0..5 {
971 deployment.record_failure();
972 }
973
974 assert!(deployment.should_abort());
975 }
976
977 #[tokio::test]
978 async fn test_rolling_update_immediate() {
979 let registry = Arc::new(VersionRegistry::new());
980 let v1_0_0 = Version::new(1, 0, 0);
981 let v1_1_0 = Version::new(1, 1, 0);
982 let dna = Dna::new(vec![1, 2, 3, 4]);
983
984 registry
985 .register_version(VersionMetadata::new(
986 v1_0_0,
987 dna.clone(),
988 "v1.0.0".to_string(),
989 ))
990 .unwrap();
991 registry
992 .register_version(VersionMetadata::new(v1_1_0, dna, "v1.1.0".to_string()))
993 .unwrap();
994
995 let mut agents = Vec::new();
996 for _ in 0..10 {
997 let agent_id = AgentId::new_v4();
998 registry.register_agent(agent_id, v1_0_0).unwrap();
999 agents.push(agent_id);
1000 }
1001
1002 let deployer = VersionDeployer::new(registry.clone());
1003 let config = RollingUpdateConfig::new(v1_0_0, v1_1_0)
1004 .with_strategy(RollingUpdateStrategy::Immediate);
1005
1006 let updated = deployer
1007 .rolling_update(config, agents.clone())
1008 .await
1009 .unwrap();
1010 assert_eq!(updated.len(), 10);
1011
1012 for agent_id in agents {
1013 let version = registry.get_agent_version(&agent_id).unwrap();
1014 assert_eq!(version, v1_1_0);
1015 }
1016 }
1017
1018 #[tokio::test]
1019 async fn test_rolling_update_batched() {
1020 let registry = Arc::new(VersionRegistry::new());
1021 let v1_0_0 = Version::new(1, 0, 0);
1022 let v1_1_0 = Version::new(1, 1, 0);
1023 let dna = Dna::new(vec![1, 2, 3, 4]);
1024
1025 registry
1026 .register_version(VersionMetadata::new(
1027 v1_0_0,
1028 dna.clone(),
1029 "v1.0.0".to_string(),
1030 ))
1031 .unwrap();
1032 registry
1033 .register_version(VersionMetadata::new(v1_1_0, dna, "v1.1.0".to_string()))
1034 .unwrap();
1035
1036 let mut agents = Vec::new();
1037 for _ in 0..20 {
1038 let agent_id = AgentId::new_v4();
1039 registry.register_agent(agent_id, v1_0_0).unwrap();
1040 agents.push(agent_id);
1041 }
1042
1043 let deployer = VersionDeployer::new(registry.clone());
1044 let config = RollingUpdateConfig::new(v1_0_0, v1_1_0).with_strategy(
1045 RollingUpdateStrategy::Batched {
1046 batch_size: 5,
1047 delay_between_batches_ms: 10,
1048 },
1049 );
1050
1051 let updated = deployer
1052 .rolling_update(config, agents.clone())
1053 .await
1054 .unwrap();
1055 assert_eq!(updated.len(), 20);
1056 }
1057}