mielin_cells/
versioning.rs

1//! Agent Versioning System
2//!
3//! Provides semantic versioning for agents, version-aware migration,
4//! rolling updates, A/B testing, and canary deployments.
5
6use 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/// Semantic version for agents
15///
16/// Follows semantic versioning specification (major.minor.patch)
17#[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    /// Create a new version
26    pub fn new(major: u32, minor: u32, patch: u32) -> Self {
27        Self {
28            major,
29            minor,
30            patch,
31        }
32    }
33
34    /// Check if this version is compatible with another version
35    /// Compatible if major version matches and minor version is >= target
36    pub fn is_compatible_with(&self, other: &Version) -> bool {
37        self.major == other.major && self.minor >= other.minor
38    }
39
40    /// Check if this is a breaking change from another version
41    pub fn is_breaking_change(&self, other: &Version) -> bool {
42        self.major != other.major
43    }
44
45    /// Check if this is a minor update from another version
46    pub fn is_minor_update(&self, other: &Version) -> bool {
47        self.major == other.major && self.minor != other.minor
48    }
49
50    /// Check if this is a patch update from another version
51    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    /// Get the next major version
56    pub fn next_major(&self) -> Self {
57        Self::new(self.major + 1, 0, 0)
58    }
59
60    /// Get the next minor version
61    pub fn next_minor(&self) -> Self {
62        Self::new(self.major, self.minor + 1, 0)
63    }
64
65    /// Get the next patch version
66    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/// Version-related errors
96#[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/// Metadata for a versioned agent DNA
113#[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>>, // Required intermediate versions for migration
121}
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
151/// Registry for managing agent versions
152pub 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    /// Register a new version
166    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    /// Get version metadata
173    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    /// List all registered versions
182    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    /// Get latest version
190    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    /// Register an agent with a version
196    pub fn register_agent(&self, agent_id: AgentId, version: Version) -> Result<(), VersionError> {
197        // Verify version exists
198        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    /// Get agent's version
209    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    /// Update agent's version
221    pub fn update_agent_version(
222        &self,
223        agent_id: AgentId,
224        new_version: Version,
225    ) -> Result<Version, VersionError> {
226        // Verify new version exists
227        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    /// Get agents by version
243    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    /// Check if migration is possible between versions
256    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        // Can't migrate to deprecated version
261        if to_meta.deprecated {
262            return Ok(false);
263        }
264
265        // Check compatibility
266        if to.is_compatible_with(from) {
267            return Ok(true);
268        }
269
270        // Check if there's a migration path
271        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/// Rolling update strategy
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub enum RollingUpdateStrategy {
288    /// Update all agents at once
289    Immediate,
290    /// Update agents in batches
291    Batched {
292        batch_size: usize,
293        delay_between_batches_ms: u64,
294    },
295    /// Update one agent at a time
296    Sequential { delay_between_agents_ms: u64 },
297    /// Update based on percentage of total agents
298    Percentage {
299        percentage: f32,
300        delay_between_batches_ms: u64,
301    },
302}
303
304/// Rolling update configuration
305#[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/// A/B testing configuration
352#[derive(Debug, Clone)]
353pub struct ABTestConfig {
354    pub version_a: Version,
355    pub version_b: Version,
356    pub traffic_split: f32, // Percentage of traffic to version B (0.0-1.0)
357    pub duration_seconds: Option<u64>,
358    pub metrics: Vec<String>, // Metrics to track
359}
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/// Canary deployment configuration
384#[derive(Debug, Clone)]
385pub struct CanaryConfig {
386    pub stable_version: Version,
387    pub canary_version: Version,
388    pub canary_percentage: f32, // Initial percentage for canary (0.0-1.0)
389    pub increment_percentage: f32, // How much to increase each step
390    pub increment_interval_seconds: u64,
391    pub success_threshold: f32, // Success rate threshold (0.0-1.0)
392    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, // Start with 5%
401            increment_percentage: 0.05,
402            increment_interval_seconds: 300, // 5 minutes
403            success_threshold: 0.95,         // 95% success rate
404            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
430/// Version deployment manager
431pub struct VersionDeployer {
432    registry: Arc<VersionRegistry>,
433}
434
435impl VersionDeployer {
436    pub fn new(registry: Arc<VersionRegistry>) -> Self {
437        Self { registry }
438    }
439
440    /// Perform a rolling update
441    pub async fn rolling_update(
442        &self,
443        config: RollingUpdateConfig,
444        agents: Vec<AgentId>,
445    ) -> Result<Vec<AgentId>, VersionError> {
446        // Verify versions exist and migration is possible
447        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        // Filter agents by current version
461        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    /// Rollback updates to previous version
577    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    /// Update a single agent
589    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    /// Start an A/B test
600    pub fn start_ab_test(&self, config: ABTestConfig) -> Result<ABTestDeployment, VersionError> {
601        // Verify versions exist
602        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    /// Start a canary deployment
613    pub fn start_canary(&self, config: CanaryConfig) -> Result<CanaryDeployment, VersionError> {
614        // Verify versions exist
615        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
629/// A/B test deployment state
630pub 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    /// Assign an agent to a version based on traffic split
638    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    /// Get statistics
652    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/// A/B test statistics
667#[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
674/// Canary deployment state
675pub 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    /// Assign an agent to canary or stable version
686    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    /// Record a success for canary
700    pub fn record_success(&mut self) {
701        self.success_count += 1;
702    }
703
704    /// Record a failure for canary
705    pub fn record_failure(&mut self) {
706        self.failure_count += 1;
707    }
708
709    /// Check if canary should be promoted
710    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    /// Check if canary should be aborted
722    pub fn should_abort(&self) -> bool {
723        self.failure_count >= self.config.max_failures
724    }
725
726    /// Increment canary percentage
727    pub fn increment(&mut self) {
728        self.current_percentage =
729            (self.current_percentage + self.config.increment_percentage).min(1.0);
730    }
731
732    /// Get statistics
733    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/// Canary deployment statistics
750#[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        // Assign 100 agents
918        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        // With 50/50 split, we expect roughly equal distribution
925        // Allow for some variance due to randomness
926        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), // Allow up to 10 failures
936            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        // Record successful operations
944        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}