actr_cli/commands/
install.rs

1//! Install Command Implementation
2//!
3//! Implement install flow based on reuse architecture with check-first principle
4
5use crate::core::{
6    ActrCliError, Command, CommandContext, CommandResult, ComponentType, DependencySpec,
7    ErrorReporter, InstallResult,
8};
9use actr_config::LockFile;
10use actr_protocol::{ActrType, ActrTypeExt};
11use actr_version::{CompatibilityLevel, Fingerprint, ProtoFile, ServiceCompatibility};
12use anyhow::Result;
13use async_trait::async_trait;
14use clap::Args;
15
16/// Install command
17#[derive(Args, Debug)]
18#[command(
19    about = "Install service dependencies",
20    long_about = "Install service dependencies. You can install specific service packages, or install all dependencies configured in Actr.toml.\n\nExamples:\n  actr install                          # Install all dependencies from Actr.toml\n  actr install user-service             # Install a service by name\n  actr install my-alias --actr-type acme+EchoService  # Install with alias and explicit actr_type"
21)]
22pub struct InstallCommand {
23    /// Package name or alias (when used with --actr-type, this becomes the alias)
24    #[arg(value_name = "PACKAGE")]
25    pub packages: Vec<String>,
26
27    /// Actor type for the dependency (format: manufacturer+name, e.g., acme+EchoService).
28    /// When specified, the PACKAGE argument is treated as an alias.
29    #[arg(long, value_name = "TYPE")]
30    pub actr_type: Option<String>,
31
32    /// Service fingerprint for version pinning
33    #[arg(long, value_name = "FINGERPRINT")]
34    pub fingerprint: Option<String>,
35
36    /// Force reinstallation
37    #[arg(long)]
38    pub force: bool,
39
40    /// Force update of all dependencies
41    #[arg(long)]
42    pub force_update: bool,
43
44    /// Skip fingerprint verification
45    #[arg(long)]
46    pub skip_verification: bool,
47}
48
49/// Installation mode
50#[derive(Debug, Clone)]
51pub enum InstallMode {
52    /// Mode 1: Add new dependency (npm install <package>)
53    /// - Pull remote proto to protos/ folder
54    /// - Modify Actr.toml (add dependency)
55    /// - Update Actr.lock.toml
56    AddNewPackage { packages: Vec<String> },
57
58    /// Mode 1b: Add dependency with explicit alias and actr_type (actr install <alias> --actr-type <type>)
59    /// - Discover service by actr_type
60    /// - Use first argument as alias
61    /// - Modify Actr.toml (add dependency with alias)
62    /// - Update Actr.lock.toml
63    AddWithAlias {
64        alias: String,
65        actr_type: ActrType,
66        fingerprint: Option<String>,
67    },
68
69    /// Mode 2: Install dependencies in config (npm install)
70    /// - Do NOT modify Actr.toml
71    /// - Use lock file versions if available
72    /// - Only update Actr.lock.toml
73    InstallFromConfig { force_update: bool },
74}
75
76#[async_trait]
77impl Command for InstallCommand {
78    async fn execute(&self, context: &CommandContext) -> Result<CommandResult> {
79        // Check-First principle: validate project state first
80        if !self.is_actr_project() {
81            return Err(ActrCliError::InvalidProject {
82                message: "Not an Actor-RTC project. Run 'actr init' to initialize.".to_string(),
83            }
84            .into());
85        }
86
87        // Determine installation mode
88        let mode = if let Some(actr_type_str) = &self.actr_type {
89            // Mode 1b: Install with explicit alias and actr_type
90            if self.packages.is_empty() {
91                return Err(ActrCliError::InvalidArgument {
92                    message:
93                        "When using --actr-type, you must provide an alias as the first argument"
94                            .to_string(),
95                }
96                .into());
97            }
98            let alias = self.packages[0].clone();
99            let actr_type = ActrType::from_string_repr(actr_type_str).map_err(|_| {
100                ActrCliError::InvalidArgument {
101                    message: format!(
102                        "Invalid actr_type format '{}'. Expected format: manufacturer+name (e.g., acme+EchoService)",
103                        actr_type_str
104                    ),
105                }
106            })?;
107            InstallMode::AddWithAlias {
108                alias,
109                actr_type,
110                fingerprint: self.fingerprint.clone(),
111            }
112        } else if !self.packages.is_empty() {
113            InstallMode::AddNewPackage {
114                packages: self.packages.clone(),
115            }
116        } else {
117            InstallMode::InstallFromConfig {
118                force_update: self.force_update,
119            }
120        };
121
122        // Execute based on mode
123        match mode {
124            InstallMode::AddNewPackage { ref packages } => {
125                self.execute_add_package(context, packages).await
126            }
127            InstallMode::AddWithAlias {
128                ref alias,
129                ref actr_type,
130                ref fingerprint,
131            } => {
132                self.execute_add_with_alias(context, alias, actr_type, fingerprint.as_deref())
133                    .await
134            }
135            InstallMode::InstallFromConfig { force_update } => {
136                self.execute_install_from_config(context, force_update)
137                    .await
138            }
139        }
140    }
141
142    fn required_components(&self) -> Vec<ComponentType> {
143        // Install command needs complete install pipeline components
144        vec![
145            ComponentType::ConfigManager,
146            ComponentType::DependencyResolver,
147            ComponentType::ServiceDiscovery,
148            ComponentType::NetworkValidator,
149            ComponentType::FingerprintValidator,
150            ComponentType::ProtoProcessor,
151            ComponentType::CacheManager,
152        ]
153    }
154
155    fn name(&self) -> &str {
156        "install"
157    }
158
159    fn description(&self) -> &str {
160        "npm-style service-level dependency management (check-first architecture)"
161    }
162}
163
164impl InstallCommand {
165    pub fn new(
166        packages: Vec<String>,
167        actr_type: Option<String>,
168        fingerprint: Option<String>,
169        force: bool,
170        force_update: bool,
171        skip_verification: bool,
172    ) -> Self {
173        Self {
174            packages,
175            actr_type,
176            fingerprint,
177            force,
178            force_update,
179            skip_verification,
180        }
181    }
182
183    // Create from clap Args
184    pub fn from_args(args: &InstallCommand) -> Self {
185        InstallCommand {
186            packages: args.packages.clone(),
187            actr_type: args.actr_type.clone(),
188            fingerprint: args.fingerprint.clone(),
189            force: args.force,
190            force_update: args.force_update,
191            skip_verification: args.skip_verification,
192        }
193    }
194
195    /// Check if in Actor-RTC project
196    fn is_actr_project(&self) -> bool {
197        std::path::Path::new("Actr.toml").exists()
198    }
199
200    /// Execute Mode 1: Add new package (actr install <package>)
201    /// - Pull remote proto to protos/ folder
202    /// - Modify Actr.toml (add dependency)
203    /// - Update Actr.lock.toml
204    async fn execute_add_package(
205        &self,
206        context: &CommandContext,
207        packages: &[String],
208    ) -> Result<CommandResult> {
209        println!("actr install {}", packages.join(" "));
210
211        let install_pipeline = {
212            let mut container = context.container.lock().unwrap();
213            container.get_install_pipeline()?
214        };
215
216        let mut resolved_specs = Vec::new();
217
218        println!("🔍 Phase 1: Complete Validation");
219        for package in packages {
220            // Phase 1: Check-First validation
221            println!("  ├─ 📋 Parsing dependency spec: {}", package);
222
223            // Discover service details
224            let service_details = install_pipeline
225                .validation_pipeline()
226                .service_discovery()
227                .get_service_details(package)
228                .await?;
229
230            println!(
231                "  ├─ 🔍 Service discovery: fingerprint {}",
232                service_details.info.fingerprint
233            );
234
235            // Connectivity check
236            let connectivity = install_pipeline
237                .validation_pipeline()
238                .network_validator()
239                .check_connectivity(package)
240                .await?;
241
242            if connectivity.is_reachable {
243                println!("  ├─ 🌐 Network connectivity test ✅");
244            } else {
245                println!("  └─ ❌ Network connection failed");
246                return Err(anyhow::anyhow!(
247                    "Network connectivity test failed for {}",
248                    package,
249                ));
250            }
251
252            // Fingerprint check
253            println!("  ├─ 🔐 Fingerprint integrity verification ✅");
254
255            // Create dependency spec with resolved info
256            let resolved_spec = DependencySpec {
257                alias: package.clone(),
258                actr_type: Some(service_details.info.actr_type.clone()),
259                name: package.clone(),
260                fingerprint: Some(service_details.info.fingerprint.clone()),
261            };
262            resolved_specs.push(resolved_spec);
263            println!("  └─ ✅ Added to installation plan");
264            println!();
265        }
266
267        if resolved_specs.is_empty() {
268            return Ok(CommandResult::Success("No packages to install".to_string()));
269        }
270
271        // Phase 2: Atomic installation
272        println!("📝 Phase 2: Atomic Installation");
273
274        // Execute installation for all packages
275        match install_pipeline.install_dependencies(&resolved_specs).await {
276            Ok(result) => {
277                println!("  ├─ 💾 Backing up current configuration");
278                println!("  ├─ 📝 Updating Actr.toml configuration ✅");
279                println!("  ├─ đŸ“Ļ Caching proto files ✅");
280                println!("  ├─ 🔒 Updating Actr.lock.toml ✅");
281                println!("  └─ ✅ Installation completed");
282                println!();
283                self.display_install_success(&result);
284                Ok(CommandResult::Install(result))
285            }
286            Err(e) => {
287                println!("  └─ 🔄 Restoring backup (due to installation failure)");
288                let cli_error = ActrCliError::InstallFailed {
289                    reason: e.to_string(),
290                };
291                eprintln!("{}", ErrorReporter::format_error(&cli_error));
292                Err(e)
293            }
294        }
295    }
296
297    /// Execute Mode 1b: Add dependency with explicit alias and actr_type
298    /// - Discover service by actr_type
299    /// - Use provided alias
300    /// - Modify Actr.toml (add dependency with alias)
301    /// - Update Actr.lock.toml
302    async fn execute_add_with_alias(
303        &self,
304        context: &CommandContext,
305        alias: &str,
306        actr_type: &ActrType,
307        fingerprint: Option<&str>,
308    ) -> Result<CommandResult> {
309        use actr_protocol::ActrTypeExt;
310
311        println!(
312            "actr install {} --actr-type {}",
313            alias,
314            actr_type.to_string_repr()
315        );
316
317        let install_pipeline = {
318            let mut container = context.container.lock().unwrap();
319            container.get_install_pipeline()?
320        };
321
322        println!("🔍 Phase 1: Complete Validation");
323        println!("  ├─ 📋 Alias: {}", alias);
324        println!("  ├─ đŸˇī¸  Actor Type: {}", actr_type.to_string_repr());
325
326        // Discover service by actr_type
327        let service_discovery = install_pipeline.validation_pipeline().service_discovery();
328
329        // Find service matching the actr_type
330        let services = service_discovery.discover_services(None).await?;
331        let matching_service = services
332            .iter()
333            .find(|s| s.actr_type == *actr_type)
334            .ok_or_else(|| ActrCliError::ServiceNotFound {
335                name: actr_type.to_string_repr(),
336            })?;
337
338        let service_name = matching_service.name.clone();
339        println!("  ├─ 🔍 Service discovered: {}", service_name);
340
341        // Get full service details
342        let service_details = service_discovery.get_service_details(&service_name).await?;
343
344        println!(
345            "  ├─ 🔍 Service fingerprint: {}",
346            service_details.info.fingerprint
347        );
348
349        // Verify fingerprint if provided
350        if let Some(expected_fp) = fingerprint {
351            if service_details.info.fingerprint != expected_fp {
352                println!("  └─ ❌ Fingerprint mismatch");
353                return Err(ActrCliError::FingerprintMismatch {
354                    expected: expected_fp.to_string(),
355                    actual: service_details.info.fingerprint.clone(),
356                }
357                .into());
358            }
359            println!("  ├─ 🔐 Fingerprint verification ✅");
360        }
361
362        // Connectivity check
363        let connectivity = install_pipeline
364            .validation_pipeline()
365            .network_validator()
366            .check_connectivity(&service_name)
367            .await?;
368
369        if connectivity.is_reachable {
370            println!("  ├─ 🌐 Network connectivity test ✅");
371        } else {
372            println!("  └─ ❌ Network connection failed");
373            return Err(anyhow::anyhow!(
374                "Network connectivity test failed for {}",
375                service_name,
376            ));
377        }
378
379        // Create dependency spec with alias
380        let resolved_spec = DependencySpec {
381            alias: alias.to_string(),
382            actr_type: Some(service_details.info.actr_type.clone()),
383            name: service_name.clone(),
384            fingerprint: Some(
385                fingerprint
386                    .map(|s| s.to_string())
387                    .unwrap_or_else(|| service_details.info.fingerprint.clone()),
388            ),
389        };
390
391        println!("  └─ ✅ Added to installation plan");
392        println!();
393
394        // Phase 2: Atomic installation
395        println!("📝 Phase 2: Atomic Installation");
396
397        // Execute installation
398        match install_pipeline
399            .install_dependencies(&[resolved_spec])
400            .await
401        {
402            Ok(result) => {
403                println!("  ├─ 💾 Backing up current configuration");
404                println!("  ├─ 📝 Updating Actr.toml configuration ✅");
405                println!("  ├─ đŸ“Ļ Caching proto files ✅");
406                println!("  ├─ 🔒 Updating Actr.lock.toml ✅");
407                println!("  └─ ✅ Installation completed");
408                println!();
409                self.display_install_success(&result);
410                Ok(CommandResult::Install(result))
411            }
412            Err(e) => {
413                println!("  └─ 🔄 Restoring backup (due to installation failure)");
414                let cli_error = ActrCliError::InstallFailed {
415                    reason: e.to_string(),
416                };
417                eprintln!("{}", ErrorReporter::format_error(&cli_error));
418                Err(e)
419            }
420        }
421    }
422
423    /// Execute Mode 2: Install from config (actr install)
424    /// - Do NOT modify Actr.toml
425    /// - Use lock file versions if available
426    /// - Check for compatibility conflicts when lock file exists
427    /// - Only update Actr.lock.toml
428    async fn execute_install_from_config(
429        &self,
430        context: &CommandContext,
431        force_update: bool,
432    ) -> Result<CommandResult> {
433        if force_update || self.force {
434            println!("đŸ“Ļ Force updating all service dependencies");
435        } else {
436            println!("đŸ“Ļ Installing service dependencies from config");
437        }
438        println!();
439
440        // Load dependencies from Actr.toml
441        let dependency_specs = self.load_dependencies_from_config(context).await?;
442
443        if dependency_specs.is_empty() {
444            println!("â„šī¸ No dependencies configured, generating empty lock file");
445
446            // Generate empty lock file with metadata
447            let install_pipeline = {
448                let mut container = context.container.lock().unwrap();
449                container.get_install_pipeline()?
450            };
451            let project_root = install_pipeline.config_manager().get_project_root();
452            let lock_file_path = project_root.join("Actr.lock.toml");
453
454            let mut lock_file = LockFile::new();
455            lock_file.update_timestamp();
456            lock_file
457                .save_to_file(&lock_file_path)
458                .map_err(|e| ActrCliError::InstallFailed {
459                    reason: format!("Failed to save lock file: {}", e),
460                })?;
461
462            println!("  └─ 🔒 Generated Actr.lock.toml");
463            return Ok(CommandResult::Success(
464                "Generated empty lock file".to_string(),
465            ));
466        }
467
468        // Check for duplicate actr_type conflicts
469        let conflicts = self.check_actr_type_conflicts(&dependency_specs);
470        if !conflicts.is_empty() {
471            println!("❌ Dependency conflict detected:");
472            for conflict in &conflicts {
473                println!("   â€ĸ {}", conflict);
474            }
475            println!();
476            println!(
477                "💡 Tip: Each actr_type can only be used once. Please use different aliases for different services or remove duplicate dependencies."
478            );
479            return Err(ActrCliError::DependencyConflict {
480                message: format!(
481                    "{} dependency conflict(s) detected. Each actr_type must be unique.",
482                    conflicts.len()
483                ),
484            }
485            .into());
486        }
487
488        println!("🔍 Phase 1: Full Validation");
489        for spec in &dependency_specs {
490            println!("  ├─ 📋 Parsing dependency: {}", spec.alias);
491        }
492
493        // Get install pipeline
494        let install_pipeline = {
495            let mut container = context.container.lock().unwrap();
496            container.get_install_pipeline()?
497        };
498
499        // Check for compatibility conflicts when lock file exists (unless force_update)
500        if !force_update && !self.force {
501            let project_root = install_pipeline.config_manager().get_project_root();
502            let lock_file_path = project_root.join("Actr.lock.toml");
503            if lock_file_path.exists() {
504                println!("  ├─ 🔒 Lock file found, checking compatibility...");
505
506                // Perform compatibility check
507                let conflicts = self
508                    .check_lock_file_compatibility(
509                        &lock_file_path,
510                        &dependency_specs,
511                        &install_pipeline,
512                    )
513                    .await?;
514
515                if !conflicts.is_empty() {
516                    println!("  └─ ❌ Compatibility conflicts detected");
517                    println!();
518                    println!("âš ī¸  Breaking changes detected:");
519                    for conflict in &conflicts {
520                        println!("   â€ĸ {}", conflict);
521                    }
522                    println!();
523                    println!(
524                        "💡 Tip: Use --force-update to override and update to the latest versions"
525                    );
526                    return Err(ActrCliError::CompatibilityConflict {
527                        message: format!(
528                            "{} breaking change(s) detected. Use --force-update to override.",
529                            conflicts.len()
530                        ),
531                    }
532                    .into());
533                }
534                println!("  ├─ ✅ Compatibility check passed");
535            }
536        }
537
538        // Verify fingerprints match registered services (unless --force is used)
539        println!("  ├─ ✅ Verifying fingerprints...");
540        let fingerprint_mismatches = self
541            .verify_fingerprints(&dependency_specs, &install_pipeline)
542            .await?;
543
544        if !fingerprint_mismatches.is_empty() && !self.force {
545            println!("  └─ ❌ Fingerprint mismatch detected");
546            println!();
547            println!("âš ī¸  Fingerprint mismatch:");
548            for mismatch in &fingerprint_mismatches {
549                println!("   â€ĸ {}", mismatch);
550            }
551            println!();
552            println!(
553                "💡 Tip: Use --force to update Actr.toml with the current service fingerprints"
554            );
555            return Err(ActrCliError::FingerprintValidation {
556                message: format!(
557                    "{} fingerprint mismatch(es) detected. Use --force to update.",
558                    fingerprint_mismatches.len()
559                ),
560            }
561            .into());
562        }
563
564        // If --force is used and there are mismatches, update Actr.toml
565        if !fingerprint_mismatches.is_empty() && self.force {
566            println!("  ├─ âš ī¸  Fingerprint mismatch detected, updating Actr.toml...");
567            self.update_config_fingerprints(context, &dependency_specs, &install_pipeline)
568                .await?;
569            println!("  ├─ ✅ Actr.toml updated with current fingerprints");
570
571            // Reload dependency specs with updated fingerprints
572            let dependency_specs = self.load_dependencies_from_config(context).await?;
573
574            println!("  ├─ 🔍 Service discovery (DiscoveryRequest)");
575            println!("  ├─ 🌐 Network connectivity test");
576            println!("  └─ ✅ Installation plan generated");
577            println!();
578
579            // Execute installation with updated specs
580            println!("📝 Phase 2: Atomic Installation");
581            return match install_pipeline
582                .install_dependencies(&dependency_specs)
583                .await
584            {
585                Ok(install_result) => {
586                    println!("  ├─ 📚 Caching proto files ✅");
587                    println!("  ├─ 🔒 Updating Actr.lock.toml ✅");
588                    println!("  └─ ✅ Installation completed");
589                    println!();
590                    println!(
591                        "📝 Note: Actr.toml fingerprints were updated to match current services"
592                    );
593                    self.display_install_success(&install_result);
594                    Ok(CommandResult::Install(install_result))
595                }
596                Err(e) => {
597                    println!("  └─ ❌ Installation failed");
598                    let cli_error = ActrCliError::InstallFailed {
599                        reason: e.to_string(),
600                    };
601                    eprintln!("{}", ErrorReporter::format_error(&cli_error));
602                    Err(e)
603                }
604            };
605        }
606
607        println!("  ├─ ✅ Fingerprint verification passed");
608        println!("  ├─ 🔍 Service discovery (DiscoveryRequest)");
609        println!("  ├─ 🌐 Network connectivity test");
610        println!("  └─ ✅ Installation plan generated");
611        println!();
612
613        // Execute check-first install flow (Mode 2: no config update)
614        println!("📝 Phase 2: Atomic Installation");
615        match install_pipeline
616            .install_dependencies(&dependency_specs)
617            .await
618        {
619            Ok(install_result) => {
620                println!("  ├─ đŸ“Ļ Caching proto files ✅");
621                println!("  ├─ 🔒 Updating Actr.lock.toml ✅");
622                println!("  └─ ✅ Installation completed");
623                println!();
624                self.display_install_success(&install_result);
625                Ok(CommandResult::Install(install_result))
626            }
627            Err(e) => {
628                println!("  └─ ❌ Installation failed");
629                let cli_error = ActrCliError::InstallFailed {
630                    reason: e.to_string(),
631                };
632                eprintln!("{}", ErrorReporter::format_error(&cli_error));
633                Err(e)
634            }
635        }
636    }
637
638    /// Load dependencies from config file
639    async fn load_dependencies_from_config(
640        &self,
641        context: &CommandContext,
642    ) -> Result<Vec<DependencySpec>> {
643        let config_manager = {
644            let container = context.container.lock().unwrap();
645            container.get_config_manager()?
646        };
647        let config = config_manager
648            .load_config(
649                config_manager
650                    .get_project_root()
651                    .join("Actr.toml")
652                    .as_path(),
653            )
654            .await?;
655
656        let specs: Vec<DependencySpec> = config
657            .dependencies
658            .into_iter()
659            .map(|dependency| DependencySpec {
660                alias: dependency.alias,
661                actr_type: dependency.actr_type,
662                name: dependency.name,
663                fingerprint: dependency.fingerprint,
664            })
665            .collect();
666
667        Ok(specs)
668    }
669
670    /// Check for duplicate actr_type conflicts in dependencies
671    fn check_actr_type_conflicts(&self, specs: &[DependencySpec]) -> Vec<String> {
672        use std::collections::HashMap;
673
674        let mut actr_type_map: HashMap<String, Vec<&str>> = HashMap::new();
675        let mut conflicts = Vec::new();
676
677        for spec in specs {
678            if let Some(ref actr_type) = spec.actr_type {
679                let type_str = actr_type.to_string_repr();
680                actr_type_map.entry(type_str).or_default().push(&spec.alias);
681            }
682        }
683
684        for (actr_type, aliases) in actr_type_map {
685            if aliases.len() > 1 {
686                conflicts.push(format!(
687                    "actr_type '{}' is used by multiple dependencies: {}",
688                    actr_type,
689                    aliases.join(", ")
690                ));
691            }
692        }
693
694        conflicts
695    }
696
697    /// Verify that fingerprints in Actr.toml match the currently registered services
698    async fn verify_fingerprints(
699        &self,
700        specs: &[DependencySpec],
701        install_pipeline: &std::sync::Arc<crate::core::InstallPipeline>,
702    ) -> Result<Vec<String>> {
703        let mut mismatches = Vec::new();
704        let service_discovery = install_pipeline.validation_pipeline().service_discovery();
705
706        for spec in specs {
707            // Only check if fingerprint is specified in Actr.toml
708            let expected_fingerprint = match &spec.fingerprint {
709                Some(fp) => fp,
710                None => continue,
711            };
712
713            // Get current service details
714            let current_service = match service_discovery.get_service_details(&spec.name).await {
715                Ok(s) => s,
716                Err(e) => {
717                    mismatches.push(format!(
718                        "{}: Service not found or unavailable ({})",
719                        spec.alias, e
720                    ));
721                    continue;
722                }
723            };
724
725            let current_fingerprint = &current_service.info.fingerprint;
726
727            // Compare fingerprints
728            if expected_fingerprint != current_fingerprint {
729                mismatches.push(format!(
730                    "{}: Expected fingerprint '{}', but service has '{}'",
731                    spec.alias, expected_fingerprint, current_fingerprint
732                ));
733            }
734        }
735
736        Ok(mismatches)
737    }
738
739    /// Update Actr.toml with current service fingerprints
740    async fn update_config_fingerprints(
741        &self,
742        _context: &CommandContext,
743        specs: &[DependencySpec],
744        install_pipeline: &std::sync::Arc<crate::core::InstallPipeline>,
745    ) -> Result<()> {
746        let service_discovery = install_pipeline.validation_pipeline().service_discovery();
747        let config_manager = install_pipeline.config_manager();
748
749        // Update fingerprints for each dependency that has one specified
750        for spec in specs {
751            if spec.fingerprint.is_none() {
752                continue;
753            }
754
755            // Get current service fingerprint
756            let current_service = match service_discovery.get_service_details(&spec.name).await {
757                Ok(s) => s,
758                Err(_) => continue,
759            };
760
761            let old_fingerprint = spec
762                .fingerprint
763                .clone()
764                .unwrap_or_else(|| "none".to_string());
765            let new_fingerprint = current_service.info.fingerprint.clone();
766
767            // Create updated spec with new fingerprint
768            let updated_spec = DependencySpec {
769                alias: spec.alias.clone(),
770                name: spec.name.clone(),
771                actr_type: spec.actr_type.clone(),
772                fingerprint: Some(new_fingerprint.clone()),
773            };
774
775            // Use update_dependency to modify Actr.toml directly
776            config_manager.update_dependency(&updated_spec).await?;
777
778            println!(
779                "   📝 Updated '{}' fingerprint: {} → {}",
780                spec.alias, old_fingerprint, new_fingerprint
781            );
782        }
783
784        Ok(())
785    }
786
787    /// Check compatibility between locked dependencies and currently registered services
788    ///
789    /// This method compares the fingerprints stored in the lock file with the fingerprints
790    /// of the services currently registered on the signaling server. If a service's proto
791    /// definition has breaking changes compared to the locked version, a conflict is reported.
792    async fn check_lock_file_compatibility(
793        &self,
794        lock_file_path: &std::path::Path,
795        dependency_specs: &[DependencySpec],
796        install_pipeline: &std::sync::Arc<crate::core::InstallPipeline>,
797    ) -> Result<Vec<String>> {
798        use actr_protocol::ServiceSpec;
799
800        let mut conflicts = Vec::new();
801
802        // Load lock file
803        let lock_file = match LockFile::from_file(lock_file_path) {
804            Ok(lf) => lf,
805            Err(e) => {
806                tracing::warn!("Failed to parse lock file: {}", e);
807                return Ok(conflicts); // If we can't parse lock file, skip compatibility check
808            }
809        };
810
811        // For each dependency, check if the currently registered service is compatible
812        for spec in dependency_specs {
813            // Find the locked dependency by name
814            let locked_dep = lock_file.dependencies.iter().find(|d| d.name == spec.name);
815
816            let locked_dep = match locked_dep {
817                Some(d) => d,
818                None => {
819                    // Dependency not in lock file, skip (will be newly installed)
820                    tracing::debug!("Dependency '{}' not in lock file, skipping", spec.name);
821                    continue;
822                }
823            };
824
825            let locked_fingerprint = &locked_dep.fingerprint;
826
827            // Get current service details from the registry
828            let service_discovery = install_pipeline.validation_pipeline().service_discovery();
829            let current_service = match service_discovery.get_service_details(&spec.name).await {
830                Ok(s) => s,
831                Err(e) => {
832                    tracing::warn!("Failed to get service details for '{}': {}", spec.name, e);
833                    continue;
834                }
835            };
836
837            let current_fingerprint = &current_service.info.fingerprint;
838
839            // If fingerprints match, no need for deep analysis
840            if locked_fingerprint == current_fingerprint {
841                tracing::debug!(
842                    "Fingerprint match for '{}', no compatibility check needed",
843                    spec.name
844                );
845                continue;
846            }
847
848            // Fingerprints differ - perform deep compatibility analysis using actr-version
849            tracing::info!(
850                "Fingerprint mismatch for '{}': locked={}, current={}",
851                spec.name,
852                locked_fingerprint,
853                current_fingerprint
854            );
855
856            // Build ServiceSpec from locked proto content for comparison
857            // Note: Since lock file only stores metadata (not full proto content),
858            // we need to use semantic fingerprint comparison for compatibility check
859
860            // Convert current service proto files to actr-version ProtoFile format
861            let current_proto_files: Vec<ProtoFile> = current_service
862                .proto_files
863                .iter()
864                .map(|pf| ProtoFile {
865                    name: pf.name.clone(),
866                    content: pf.content.clone(),
867                    path: Some(pf.path.to_string_lossy().to_string()),
868                })
869                .collect();
870
871            // Calculate current service's semantic fingerprint
872            let current_semantic_fp =
873                match Fingerprint::calculate_service_semantic_fingerprint(&current_proto_files) {
874                    Ok(fp) => fp,
875                    Err(e) => {
876                        tracing::warn!(
877                            "Failed to calculate semantic fingerprint for '{}': {}",
878                            spec.name,
879                            e
880                        );
881                        // If we can't calculate fingerprint, report as potential conflict
882                        conflicts.push(format!(
883                            "{}: Unable to verify compatibility (fingerprint calculation failed)",
884                            spec.name
885                        ));
886                        continue;
887                    }
888                };
889
890            // Compare fingerprints using semantic analysis
891            // The locked fingerprint should be a service_semantic fingerprint
892            let locked_semantic = if locked_fingerprint.starts_with("service_semantic:") {
893                locked_fingerprint
894                    .strip_prefix("service_semantic:")
895                    .unwrap_or(locked_fingerprint)
896            } else {
897                locked_fingerprint.as_str()
898            };
899
900            if current_semantic_fp != locked_semantic {
901                // Semantic fingerprints differ - this indicates breaking changes
902                // Build ServiceSpec structures for detailed comparison
903                let locked_spec = ServiceSpec {
904                    name: spec.name.clone(),
905                    description: locked_dep.description.clone(),
906                    fingerprint: locked_fingerprint.clone(),
907                    protobufs: locked_dep
908                        .files
909                        .iter()
910                        .map(|pf| actr_protocol::service_spec::Protobuf {
911                            package: pf.path.clone(),
912                            content: String::new(), // Lock file doesn't store content
913                            fingerprint: pf.fingerprint.clone(),
914                        })
915                        .collect(),
916                    published_at: locked_dep.published_at,
917                    tags: locked_dep.tags.clone(),
918                };
919
920                let current_spec = ServiceSpec {
921                    name: spec.name.clone(),
922                    description: Some(current_service.info.description.clone().unwrap_or_default()),
923                    fingerprint: format!("service_semantic:{}", current_semantic_fp),
924                    protobufs: current_proto_files
925                        .iter()
926                        .map(|pf| actr_protocol::service_spec::Protobuf {
927                            package: pf.name.clone(),
928                            content: pf.content.clone(),
929                            fingerprint: String::new(),
930                        })
931                        .collect(),
932                    published_at: current_service.info.published_at,
933                    tags: current_service.info.tags.clone(),
934                };
935
936                // Attempt to analyze compatibility
937                match ServiceCompatibility::analyze_compatibility(&locked_spec, &current_spec) {
938                    Ok(analysis) => {
939                        match analysis.level {
940                            CompatibilityLevel::BreakingChanges => {
941                                let change_summary = analysis
942                                    .breaking_changes
943                                    .iter()
944                                    .map(|c| c.message.clone())
945                                    .collect::<Vec<_>>()
946                                    .join("; ");
947
948                                conflicts.push(format!(
949                                    "{}: Breaking changes detected - {}",
950                                    spec.name, change_summary
951                                ));
952                            }
953                            CompatibilityLevel::BackwardCompatible => {
954                                tracing::info!(
955                                    "Service '{}' has backward compatible changes",
956                                    spec.name
957                                );
958                                // Backward compatible is allowed, no conflict
959                            }
960                            CompatibilityLevel::FullyCompatible => {
961                                // This shouldn't happen if fingerprints differ, but handle it
962                                tracing::debug!(
963                                    "Service '{}' is fully compatible despite fingerprint difference",
964                                    spec.name
965                                );
966                            }
967                        }
968                    }
969                    Err(e) => {
970                        // If detailed analysis fails, report based on fingerprint difference
971                        tracing::warn!("Compatibility analysis failed for '{}': {}", spec.name, e);
972                        conflicts.push(format!(
973                            "{}: Service definition changed (locked: {}, current: {})",
974                            spec.name, locked_fingerprint, current_fingerprint
975                        ));
976                    }
977                }
978            }
979        }
980
981        Ok(conflicts)
982    }
983
984    /// Display install success information
985    fn display_install_success(&self, result: &InstallResult) {
986        println!();
987        println!("✅ Installation successful!");
988        println!(
989            "   đŸ“Ļ Installed dependencies: {}",
990            result.installed_dependencies.len()
991        );
992        println!("   đŸ—‚ī¸  Cache updates: {}", result.cache_updates);
993
994        if result.updated_config {
995            println!("   📝 Configuration file updated");
996        }
997
998        if result.updated_lock_file {
999            println!("   🔒 Lock file updated");
1000        }
1001
1002        if !result.warnings.is_empty() {
1003            println!();
1004            println!("âš ī¸  Warnings:");
1005            for warning in &result.warnings {
1006                println!("   â€ĸ {warning}");
1007            }
1008        }
1009
1010        println!();
1011        println!("💡 Tip: Run 'actr gen' to generate the latest code");
1012    }
1013}
1014
1015impl Default for InstallCommand {
1016    fn default() -> Self {
1017        Self::new(Vec::new(), None, None, false, false, false)
1018    }
1019}