Skip to main content

scirs2_core/ecosystem/validation/
mod.rs

1// Copyright 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::apiversioning::Version;
5use crate::error::{CoreError, CoreResult, ErrorContext};
6use std::collections::{HashMap, HashSet};
7use std::sync::{Arc, RwLock};
8use std::time::{Duration, Instant};
9/// Global ecosystem validator instance
10static GLOBAL_VALIDATOR: std::sync::OnceLock<Arc<EcosystemValidator>> = std::sync::OnceLock::new();
11/// Comprehensive ecosystem validator for production environments
12#[derive(Debug)]
13pub struct EcosystemValidator {
14    registry: Arc<RwLock<ModuleRegistry>>,
15    compatibilitymatrix: Arc<RwLock<CompatibilityMatrix>>,
16    validation_cache: Arc<RwLock<ValidationCache>>,
17    policies: Arc<RwLock<ValidationPolicies>>,
18}
19#[allow(dead_code)]
20impl EcosystemValidator {
21    /// Create new ecosystem validator
22    pub fn new() -> CoreResult<Self> {
23        Ok(Self {
24            registry: Arc::new(RwLock::new(ModuleRegistry::new())),
25            compatibilitymatrix: Arc::new(RwLock::new(CompatibilityMatrix::new())),
26            validation_cache: Arc::new(RwLock::new(ValidationCache::new())),
27            policies: Arc::new(RwLock::new(ValidationPolicies::default())),
28        })
29    }
30    /// Get global validator instance
31    pub fn global() -> CoreResult<Arc<Self>> {
32        Ok(GLOBAL_VALIDATOR
33            .get_or_init(|| Arc::new(Self::new().expect("Operation failed")))
34            .clone())
35    }
36    /// Register a module in the ecosystem
37    pub fn register_module(&self, module: ModuleInfo) -> CoreResult<()> {
38        let mut registry = self.registry.write().map_err(|_| {
39            CoreError::InvalidState(crate::error::ErrorContext {
40                message: "Failed to acquire registry lock".to_string(),
41                location: None,
42                cause: None,
43            })
44        })?;
45        registry.register(module)?;
46        let mut cache = self.validation_cache.write().map_err(|_| {
47            CoreError::InvalidState(ErrorContext {
48                message: "Failed to acquire cache lock".to_string(),
49                location: Some(crate::error::ErrorLocation::new(file!(), line!())),
50                cause: None,
51            })
52        })?;
53        cache.invalidate_module_related_cache();
54        Ok(())
55    }
56    /// Validate entire ecosystem compatibility
57    pub fn validate_ecosystem(&self) -> CoreResult<EcosystemValidationResult> {
58        let start_time = Instant::now();
59        {
60            let cache = self.validation_cache.read().map_err(|_| {
61                CoreError::InvalidState(ErrorContext {
62                    message: "Failed to acquire cache lock".to_string(),
63                    location: Some(crate::error::ErrorLocation::new(file!(), line!())),
64                    cause: None,
65                })
66            })?;
67            if let Some(cachedresult) = cache.get_ecosystem_validation() {
68                if cachedresult.is_recent(Duration::from_secs(300)) {
69                    return Ok(cachedresult.result.clone());
70                }
71            }
72        }
73        let registry = self.registry.read().map_err(|_| {
74            CoreError::InvalidState(crate::error::ErrorContext {
75                message: "Failed to acquire registry lock".to_string(),
76                location: None,
77                cause: None,
78            })
79        })?;
80        let policies = self.policies.read().map_err(|_| {
81            CoreError::InvalidState(ErrorContext {
82                message: "Failed to acquire policies lock".to_string(),
83                location: Some(crate::error::ErrorLocation::new(file!(), line!())),
84                cause: None,
85            })
86        })?;
87        let mut result = EcosystemValidationResult::new();
88        for module in registry.all_modules() {
89            let moduleresult = self.validate_module_internal(&registry, module, &policies)?;
90            result.add_moduleresult(module.name.clone(), moduleresult);
91        }
92        let compatibilityresult = self.validate_inter_module_compatibility(&registry, &policies)?;
93        result.add_compatibilityresult(compatibilityresult);
94        let api_stabilityresult = self.validate_api_stability(&registry, &policies)?;
95        result.add_api_stabilityresult(api_stabilityresult);
96        let version_consistencyresult = self.validate_version_consistency(&registry)?;
97        result.add_version_consistencyresult(version_consistencyresult);
98        result.validation_time = start_time.elapsed();
99        result.timestamp = Instant::now();
100        {
101            let mut cache = self.validation_cache.write().map_err(|_| {
102                CoreError::InvalidState(ErrorContext {
103                    message: "Failed to acquire cache lock".to_string(),
104                    location: Some(crate::error::ErrorLocation::new(file!(), line!())),
105                    cause: None,
106                })
107            })?;
108            cache.cache_ecosystem_validation(result.clone());
109        }
110        Ok(result)
111    }
112    /// Validate specific module compatibility with ecosystem
113    pub fn validate_module(&self, modulename: &str) -> CoreResult<ModuleValidationResult> {
114        let registry = self.registry.read().map_err(|_| {
115            CoreError::InvalidState(crate::error::ErrorContext {
116                message: "Failed to acquire registry lock".to_string(),
117                location: None,
118                cause: None,
119            })
120        })?;
121        let policies = self.policies.read().map_err(|_| {
122            CoreError::InvalidState(ErrorContext {
123                message: "Failed to acquire policies lock".to_string(),
124                location: Some(crate::error::ErrorLocation::new(file!(), line!())),
125                cause: None,
126            })
127        })?;
128        let module = registry.get_module(modulename).ok_or_else(|| {
129            CoreError::ValidationError(ErrorContext {
130                message: format!("Module '{modulename}' not found in registry"),
131                location: None,
132                cause: None,
133            })
134        })?;
135        self.validate_module_internal(&registry, module, &policies)
136    }
137    fn validate_module_internal(
138        &self,
139        registry: &ModuleRegistry,
140        module: &ModuleInfo,
141        policies: &ValidationPolicies,
142    ) -> CoreResult<ModuleValidationResult> {
143        let mut result = ModuleValidationResult::new(module.name.clone());
144        if let Err(e) = Version::parse(&module.version) {
145            result.adderror(ValidationError::new(
146                ValidationErrorType::InvalidVersion,
147                format!("Invalid _version format '{}': {}", module.version, e),
148            ));
149        }
150        for dep in &module.dependencies {
151            let depresult = self.validate_dependencypolicies(registry, module, dep, policies)?;
152            if !depresult.is_valid() {
153                result.adderror(ValidationError::new(
154                    ValidationErrorType::DependencyError,
155                    format!("Dependency validation failed for '{}'", dep.name),
156                ));
157            }
158        }
159        let apiresult = validate_apisurface(&module.apisurface);
160        if !apiresult.is_valid() {
161            for breaking_change in &apiresult.breakingchanges {
162                result.adderror(ValidationError::new(
163                    ValidationErrorType::ApiCompatibility,
164                    breaking_change.clone(),
165                ));
166            }
167        }
168        for feature in &module.features {
169            if !self.is_feature_compatible(feature, policies)? {
170                result.add_warning(ValidationWarning::new(
171                    ValidationWarningType::FeatureCompatibility,
172                    format!("Feature '{feature}' may have compatibility issues"),
173                ));
174            }
175        }
176        if policies.enforce_security_checks {
177            let securityresult = self.validate_module_security(module)?;
178            if !securityresult.is_secure() {
179                result.adderror(ValidationError::new(
180                    ValidationErrorType::SecurityViolation,
181                    "Module failed security validation".to_string(),
182                ));
183            }
184        }
185        Ok(result)
186    }
187    fn validate_dependencypolicies(
188        &self,
189        registry: &ModuleRegistry,
190        module: &ModuleInfo,
191        dep: &DependencyInfo,
192        _policies: &ValidationPolicies,
193    ) -> CoreResult<DependencyValidationResult> {
194        let mut result = DependencyValidationResult::new(dep.name.clone());
195        if let Some(dep_module) = registry.get_module(&dep.name) {
196            let dep_version = Version::parse(&dep_module.version).map_err(|e| {
197                CoreError::ValidationError(ErrorContext {
198                    message: format!("Invalid dependency version: {e}"),
199                    location: None,
200                    cause: None,
201                })
202            })?;
203            if !dep.version_requirement.version(&dep_version) {
204                result.add_incompatibility(format!(
205                    "Version mismatch: required {}, found {}",
206                    dep.version_requirement, dep_version
207                ));
208            }
209            if self.has_circular_dependency(registry, &module.name, &dep.name) {
210                result.add_incompatibility("Circular dependency detected".to_string());
211            }
212        } else {
213            result.add_incompatibility("Dependency not found in ecosystem".to_string());
214        }
215        Ok(result)
216    }
217    fn validate_inter_module_compatibility(
218        &self,
219        registry: &ModuleRegistry,
220        policies: &ValidationPolicies,
221    ) -> CoreResult<CompatibilityValidationResult> {
222        let mut result = CompatibilityValidationResult::new();
223        let modules = registry.all_modules();
224        let mut matrix = self.compatibilitymatrix.write().map_err(|_| {
225            CoreError::InvalidState(ErrorContext::new(
226                "Failed to acquire matrix lock".to_string(),
227            ))
228        })?;
229        for module_a in &modules {
230            for module_b in &modules {
231                if module_a.name != module_b.name {
232                    let compatibility =
233                        self.check_module_compatibility(module_a, module_b, policies)?;
234                    (*matrix).b(&module_a.name, &module_b.name, compatibility.clone());
235                    if !compatibility.is_compatible() {
236                        result.add_incompatibility(format!(
237                            "Modules '{}' and '{}' are incompatible: {}",
238                            module_a.name,
239                            module_b.name,
240                            compatibility.reason_2()
241                        ));
242                    }
243                }
244            }
245        }
246        Ok(result)
247    }
248    fn check_module_compatibility(
249        &self,
250        module_a: &ModuleInfo,
251        module_b: &ModuleInfo,
252        policies: &ValidationPolicies,
253    ) -> CoreResult<ModuleCompatibility> {
254        let version_a = Version::parse(&module_a.version).map_err(|e| {
255            CoreError::ValidationError(ErrorContext {
256                message: format!("Invalid _version for module '{}': {}", module_a.name, e),
257                location: Some(crate::error::ErrorLocation::new(file!(), line!())),
258                cause: None,
259            })
260        })?;
261        let version_b = Version::parse(&module_b.version).map_err(|e| {
262            CoreError::ValidationError(ErrorContext {
263                message: format!("Invalid _version for module '{}': {}", module_b.name, e),
264                location: Some(crate::error::ErrorLocation::new(file!(), line!())),
265                cause: None,
266            })
267        })?;
268        if !self.areversions_compatible(&version_a, &version_b, policies) {
269            return Ok(ModuleCompatibility::incompatible(format!(
270                "Version incompatibility: {version_a} vs {version_b}"
271            )));
272        }
273        if !self.are_apis_compatible(&module_a.apisurface, &module_b.apisurface) {
274            return Ok(ModuleCompatibility::incompatible(
275                "API incompatibility".to_string(),
276            ));
277        }
278        if !self.are_features_compatible(&module_a.features, &module_b.features, policies) {
279            return Ok(ModuleCompatibility::incompatible(
280                "Feature incompatibility".to_string(),
281            ));
282        }
283        Ok(ModuleCompatibility::compatible())
284    }
285    fn validate_api_stability(
286        &self,
287        registry: &ModuleRegistry,
288        policies: &ValidationPolicies,
289    ) -> CoreResult<ApiStabilityResult> {
290        let mut result = ApiStabilityResult::new();
291        for module in registry.all_modules() {
292            let previous_apisurface = registry
293                .get_previous_version(&module.name)
294                .map(|m| &m.apisurface);
295            let stability_check = self.check_api_stability(previous_apisurface, &module.apisurface);
296            if policies.enforce_semver && !stability_check.is_stable() {
297                result.add_breaking_change(
298                    module.name.clone(),
299                    stability_check.breakingchanges().to_vec(),
300                );
301            }
302            if !self.is_api_properly_versioned(&module.apisurface) {
303                result.add_versioning_violation(
304                    module.name.clone(),
305                    "API not properly versioned".to_string(),
306                );
307            }
308        }
309        Ok(result)
310    }
311    fn validate_version_consistency(
312        &self,
313        registry: &ModuleRegistry,
314    ) -> CoreResult<VersionConsistencyResult> {
315        let mut result = VersionConsistencyResult::new();
316        let modules = registry.all_modules();
317        let mut version_map: HashMap<String, Vec<Version>> = HashMap::new();
318        for module in &modules {
319            let version = Version::parse(&module.version).map_err(|e| {
320                CoreError::ValidationError(ErrorContext {
321                    message: format!("Invalid version for module '{}': {}", module.name, e),
322                    location: Some(crate::error::ErrorLocation::new(file!(), line!())),
323                    cause: None,
324                })
325            })?;
326            version_map
327                .entry(module.name.clone())
328                .or_default()
329                .push(version);
330        }
331        for (modulename, versions) in version_map {
332            if versions.len() > 1 {
333                result.add_conflict(modulename, versions);
334            }
335        }
336        for module in &modules {
337            for dep in &module.dependencies {
338                if let Some(dep_module) = registry.get_module(&dep.name) {
339                    let dep_version = Version::parse(&dep_module.version).map_err(|e| {
340                        CoreError::ValidationError(ErrorContext {
341                            message: format!(
342                                "Invalid _version format for dependency {}: {}",
343                                dep.name, e
344                            ),
345                            location: Some(crate::error::ErrorLocation::new(file!(), line!())),
346                            cause: None,
347                        })
348                    })?;
349                    if !dep.version_requirement.version(&dep_version) {
350                        result.add_dependency_mismatch(
351                            module.name.clone(),
352                            dep.name.clone(),
353                            dep.version_requirement.clone(),
354                            dep_version,
355                        );
356                    }
357                }
358            }
359        }
360        Ok(result)
361    }
362    #[allow(dead_code)]
363    fn surface(
364        &self,
365        apisurface: &ApiSurface,
366        policies: &ValidationPolicies,
367    ) -> CoreResult<ApiValidationResult> {
368        let mut result = ApiValidationResult::new();
369        for api in &apisurface.public_apis {
370            if !self.is_api_properly_documented(api)? {
371                result.add_documentation_issue(api.name.clone());
372            }
373            if policies.enforce_semver && !self.is_api_semver_compliant(api)? {
374                result.add_semver_violation(api.name.clone());
375            }
376        }
377        for api in &apisurface.deprecated_apis {
378            if !api.has_migration_path() {
379                result.add_deprecation_issue(
380                    api.name.clone(),
381                    "No migration path provided".to_string(),
382                );
383            }
384        }
385        Ok(result)
386    }
387    fn validate_module_security(
388        &self,
389        module: &ModuleInfo,
390    ) -> CoreResult<SecurityValidationResult> {
391        let mut result = SecurityValidationResult::new(module.name.clone());
392        for dep in &module.dependencies {
393            match self.has_known_vulnerabilities(&dep.name) {
394                VulnerabilityStatus::Unsupported => {
395                    result.add_unchecked_dependency(dep.name.clone());
396                }
397            }
398        }
399        if !module.features.contains(&"security".to_string())
400            && self.requires_security_features(module)?
401        {
402            result.add_security_issue("Module should enable security features".to_string());
403        }
404        Ok(result)
405    }
406    fn is_feature_compatible(
407        &self,
408        feature: &str,
409        policies: &ValidationPolicies,
410    ) -> CoreResult<bool> {
411        Ok(!policies.incompatible_features.contains(feature))
412    }
413    #[allow(dead_code)]
414    fn is_api_properly_documented(&self, api: &ApiInfo) -> CoreResult<bool> {
415        Ok(!api.documentation.is_empty())
416    }
417    #[allow(dead_code)]
418    fn is_api_semver_compliant(&self, api: &ApiInfo) -> CoreResult<bool> {
419        Ok(api.since_version.is_some())
420    }
421    fn requires_security_features(&self, module: &ModuleInfo) -> CoreResult<bool> {
422        Ok(module.name.contains("network") || module.name.contains("auth"))
423    }
424    /// Update validation policies
425    pub fn updatepolicies(&self, newpolicies: ValidationPolicies) -> CoreResult<()> {
426        let mut policies = self.policies.write().map_err(|_| {
427            CoreError::InvalidState(ErrorContext::new(
428                "Failed to acquire policies lock".to_string(),
429            ))
430        })?;
431        *policies = newpolicies;
432        let mut cache = self.validation_cache.write().map_err(|_| {
433            CoreError::InvalidState(ErrorContext::new(
434                "Failed to acquire cache lock".to_string(),
435            ))
436        })?;
437        cache.clear();
438        Ok(())
439    }
440    /// Get ecosystem health summary
441    pub fn get_ecosystem_health(&self) -> CoreResult<EcosystemHealth> {
442        let validationresult = self.validate_ecosystem()?;
443        Ok(EcosystemHealth::from_validationresult(&validationresult))
444    }
445    /// Real cycle detection: would registering `dependency` as a dependency
446    /// of `module` create a circular dependency chain?
447    ///
448    /// This performs a DFS over the *existing* declared dependency edges
449    /// starting at `dependency`: if `module` is reachable from
450    /// `dependency`, then adding the edge `module -> dependency` would close
451    /// a cycle back to `module`. A direct self-dependency (`module ==
452    /// dependency`) is trivially a cycle.
453    ///
454    /// (A generic executable [`crate::distributed::task_graph::TaskGraph`]
455    /// exists elsewhere in this crate, but it is oriented around scheduling
456    /// closures for execution; a plain reachability walk over the
457    /// declarative [`ModuleRegistry`] data is simpler and does not require
458    /// wrapping module metadata in executable tasks.)
459    pub fn has_circular_dependency(
460        &self,
461        registry: &ModuleRegistry,
462        module: &str,
463        dependency: &str,
464    ) -> bool {
465        if module == dependency {
466            return true;
467        }
468        let mut visited: HashSet<&str> = HashSet::new();
469        let mut stack: Vec<&str> = vec![dependency];
470        while let Some(current) = stack.pop() {
471            if current == module {
472                return true;
473            }
474            if !visited.insert(current) {
475                continue;
476            }
477            if let Some(info) = registry.get_module(current) {
478                stack.extend(info.dependencies.iter().map(|dep| dep.name.as_str()));
479            }
480        }
481        false
482    }
483    /// Real semver-based pairwise version compatibility: under
484    /// `strict_version_matching` the versions must match exactly, otherwise
485    /// modules sharing a major version are considered compatible (the
486    /// standard semver contract for a stable release; see
487    /// [`crate::apiversioning::Version::is_compatible_with`] for the
488    /// single-version analogue used elsewhere in this crate).
489    pub fn areversions_compatible(
490        &self,
491        version_a: &Version,
492        version_b: &Version,
493        policies: &ValidationPolicies,
494    ) -> bool {
495        if policies.strict_version_matching {
496            version_a == version_b
497        } else {
498            version_a.major == version_b.major
499        }
500    }
501    /// Real API-presence check: flags a genuine symbol conflict, i.e. two
502    /// modules independently declaring a public API with the same name but
503    /// a different signature. Modules that share no API names, or share
504    /// names with identical signatures, are compatible.
505    pub fn are_apis_compatible(&self, api_a: &ApiSurface, apib: &ApiSurface) -> bool {
506        api_a.public_apis.iter().all(|a| {
507            apib.public_apis
508                .iter()
509                .find(|b| b.name == a.name)
510                .is_none_or(|b| b.signature == a.signature)
511        })
512    }
513    /// Real feature-compatibility check: neither module may enable a
514    /// feature that ecosystem policy has blacklisted as incompatible (the
515    /// same `incompatible_features` policy enforced per-module by the
516    /// private `is_feature_compatible` method, applied here to a module
517    /// pair).
518    pub fn are_features_compatible(
519        &self,
520        features_a: &[String],
521        featuresb: &[String],
522        policies: &ValidationPolicies,
523    ) -> bool {
524        let has_blacklisted = |features: &[String]| {
525            features
526                .iter()
527                .any(|f| policies.incompatible_features.contains(f))
528        };
529        !has_blacklisted(features_a) && !has_blacklisted(featuresb)
530    }
531    /// Real API-stability check comparing `current` against `previous` (if
532    /// any version was previously registered for this module): detects
533    /// removed public APIs and changed signatures, both breaking changes.
534    /// A module with no prior recorded version is trivially stable (there
535    /// is nothing to have broken yet).
536    pub fn check_api_stability(
537        &self,
538        previous: Option<&ApiSurface>,
539        current: &ApiSurface,
540    ) -> ApiStabilityCheck {
541        let Some(previous) = previous else {
542            return ApiStabilityCheck::new(true, vec![]);
543        };
544        let mut breakingchanges = Vec::new();
545        for prev_api in &previous.public_apis {
546            if !current
547                .public_apis
548                .iter()
549                .any(|api| api.name == prev_api.name)
550            {
551                breakingchanges.push(format!("API '{}' was removed", prev_api.name));
552            }
553        }
554        for current_api in &current.public_apis {
555            if let Some(prev_api) = previous
556                .public_apis
557                .iter()
558                .find(|api| api.name == current_api.name)
559            {
560                if current_api.signature != prev_api.signature {
561                    breakingchanges.push(format!("API '{}' signature changed", current_api.name));
562                }
563            }
564        }
565        ApiStabilityCheck::new(breakingchanges.is_empty(), breakingchanges)
566    }
567    /// Real versioning-compliance check: every public API must carry a
568    /// `since_version` annotation.
569    pub fn is_api_properly_versioned(&self, apisurface: &ApiSurface) -> bool {
570        apisurface
571            .public_apis
572            .iter()
573            .all(|api| api.since_version.is_some())
574    }
575    /// Vulnerability lookup for a dependency by name.
576    ///
577    /// This crate has no vulnerability-advisory database (e.g. a RustSec
578    /// feed) integrated, and none of `[workspace.dependencies]` provides
579    /// one; adding a live/networked lookup here would also make ecosystem
580    /// validation non-hermetic. Rather than fabricate a "no known
581    /// vulnerabilities" verdict, this honestly reports that the dependency
582    /// was never checked. See [`SecurityValidationResult::unchecked_dependencies`].
583    pub fn has_known_vulnerabilities(&self, _dependency_name: &str) -> VulnerabilityStatus {
584        VulnerabilityStatus::Unsupported
585    }
586}
587/// Outcome of a dependency vulnerability lookup. Kept as an enum (rather
588/// than `bool`) so that "never checked" can never be silently read as
589/// "confirmed clean" — see [`EcosystemValidator::has_known_vulnerabilities`].
590#[derive(Debug, Clone, Copy, PartialEq, Eq)]
591#[non_exhaustive]
592pub enum VulnerabilityStatus {
593    /// No advisory database is available in this build: this dependency's
594    /// vulnerability status is genuinely unknown, not confirmed safe.
595    Unsupported,
596}
597/// Module registry for tracking ecosystem components
598#[derive(Debug)]
599pub struct ModuleRegistry {
600    modules: HashMap<String, ModuleInfo>,
601    previousversions: HashMap<String, ModuleInfo>,
602}
603impl ModuleRegistry {
604    pub fn new() -> Self {
605        Self {
606            modules: HashMap::new(),
607            previousversions: HashMap::new(),
608        }
609    }
610    pub fn register(&mut self, module: ModuleInfo) -> CoreResult<()> {
611        if let Some(existing) = self.modules.get(&module.name) {
612            self.previousversions
613                .insert(module.name.clone(), existing.clone());
614        }
615        self.modules.insert(module.name.clone(), module);
616        Ok(())
617    }
618    pub fn get_module(&self, name: &str) -> Option<&ModuleInfo> {
619        self.modules.get(name)
620    }
621    pub fn get_previous_version(&self, name: &str) -> Option<&ModuleInfo> {
622        self.previousversions.get(name)
623    }
624    pub fn all_modules(&self) -> Vec<&ModuleInfo> {
625        self.modules.values().collect()
626    }
627    pub fn module_count(&self) -> usize {
628        self.modules.len()
629    }
630}
631impl Default for ModuleRegistry {
632    fn default() -> Self {
633        Self::new()
634    }
635}
636/// Information about a module in the ecosystem
637#[derive(Debug, Clone)]
638pub struct ModuleInfo {
639    pub name: String,
640    pub version: String,
641    pub dependencies: Vec<DependencyInfo>,
642    pub apisurface: ApiSurface,
643    pub features: Vec<String>,
644    pub metadata: ModuleMetadata,
645}
646#[derive(Debug, Clone)]
647pub struct DependencyInfo {
648    pub name: String,
649    pub version_requirement: VersionRequirement,
650    pub optional: bool,
651}
652#[derive(Debug, Clone)]
653pub struct VersionRequirement {
654    pub requirement: String,
655}
656impl VersionRequirement {
657    pub fn new(requirement: &str) -> Self {
658        Self {
659            requirement: requirement.to_string(),
660        }
661    }
662    /// Real Cargo-style semver requirement matching.
663    ///
664    /// Supports `*` (any version), bare versions (Cargo's default: treated
665    /// as a caret requirement), `^`, `~`, `=`, `>=`, `<=`, `>`, `<`, and
666    /// comma-separated conjunctions of the above (e.g. `">=1.2.0, <2.0.0"`).
667    /// An unparsable clause never matches (fails closed rather than
668    /// silently accepting an unrecognized requirement).
669    pub fn version(&self, version: &Version) -> bool {
670        let requirement = self.requirement.trim();
671        if requirement.is_empty() || requirement == "*" {
672            return true;
673        }
674        requirement
675            .split(',')
676            .map(str::trim)
677            .filter(|clause| !clause.is_empty())
678            .all(|clause| Self::clause_matches(clause, version))
679    }
680    /// Evaluate a single (non-comma-compound) requirement clause.
681    fn clause_matches(clause: &str, version: &Version) -> bool {
682        let (op, rest) = Self::split_operator(clause);
683        match Version::parse(rest.trim()) {
684            Ok(required) => match op {
685                VersionOp::Caret => Self::caret_matches(&required, version),
686                VersionOp::Tilde => Self::tilde_matches(&required, version),
687                VersionOp::Exact => *version == required,
688                VersionOp::Ge => *version >= required,
689                VersionOp::Le => *version <= required,
690                VersionOp::Gt => *version > required,
691                VersionOp::Lt => *version < required,
692            },
693            Err(_) => false,
694        }
695    }
696    /// Split a leading comparison operator off a requirement clause. A bare
697    /// version (no operator) is treated as caret, matching Cargo's default.
698    fn split_operator(clause: &str) -> (VersionOp, &str) {
699        if let Some(rest) = clause.strip_prefix(">=") {
700            (VersionOp::Ge, rest)
701        } else if let Some(rest) = clause.strip_prefix("<=") {
702            (VersionOp::Le, rest)
703        } else if let Some(rest) = clause.strip_prefix('>') {
704            (VersionOp::Gt, rest)
705        } else if let Some(rest) = clause.strip_prefix('<') {
706            (VersionOp::Lt, rest)
707        } else if let Some(rest) = clause.strip_prefix('=') {
708            (VersionOp::Exact, rest)
709        } else if let Some(rest) = clause.strip_prefix('^') {
710            (VersionOp::Caret, rest)
711        } else if let Some(rest) = clause.strip_prefix('~') {
712            (VersionOp::Tilde, rest)
713        } else {
714            (VersionOp::Caret, clause)
715        }
716    }
717    /// Cargo caret semantics: `^1.2.3` := `>=1.2.3, <2.0.0`;
718    /// `^0.2.3` := `>=0.2.3, <0.3.0`; `^0.0.3` := `=0.0.3`.
719    fn caret_matches(required: &Version, version: &Version) -> bool {
720        if version < required {
721            return false;
722        }
723        if required.major > 0 {
724            version.major == required.major
725        } else if required.minor > 0 {
726            version.major == 0 && version.minor == required.minor
727        } else {
728            version.major == 0 && version.minor == 0 && version.patch == required.patch
729        }
730    }
731    /// Cargo tilde semantics: `~1.2.3` := `>=1.2.3, <1.3.0`.
732    fn tilde_matches(required: &Version, version: &Version) -> bool {
733        version >= required && version.major == required.major && version.minor == required.minor
734    }
735}
736/// Comparison operator parsed from a [`VersionRequirement`] clause.
737#[derive(Debug, Clone, Copy, PartialEq, Eq)]
738enum VersionOp {
739    Caret,
740    Tilde,
741    Exact,
742    Ge,
743    Le,
744    Gt,
745    Lt,
746}
747impl std::fmt::Display for VersionRequirement {
748    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
749        write!(f, "{}", self.requirement)
750    }
751}
752#[derive(Debug, Clone)]
753pub struct ApiSurface {
754    pub public_apis: Vec<ApiInfo>,
755    pub deprecated_apis: Vec<DeprecatedApiInfo>,
756}
757#[derive(Debug, Clone)]
758pub struct ApiInfo {
759    pub name: String,
760    pub signature: String,
761    pub documentation: String,
762    pub since_version: Option<Version>,
763    pub stability: ApiStability,
764}
765#[derive(Debug, Clone)]
766pub struct DeprecatedApiInfo {
767    pub name: String,
768    pub deprecated_since: Version,
769    pub removal_version: Option<Version>,
770    pub migration_path: Option<String>,
771}
772impl DeprecatedApiInfo {
773    pub fn has_migration_path(&self) -> bool {
774        self.migration_path.is_some()
775    }
776}
777#[derive(Debug, Clone, Copy, PartialEq, Eq)]
778pub enum ApiStability {
779    Stable,
780    Unstable,
781    Experimental,
782}
783#[derive(Debug, Clone)]
784pub struct ModuleMetadata {
785    pub author: String,
786    pub description: String,
787    pub license: String,
788    pub repository: Option<String>,
789    pub build_time: Option<String>,
790}
791/// Compatibility matrix for module interactions
792#[derive(Debug)]
793pub struct CompatibilityMatrix {
794    matrix: HashMap<(String, String), ModuleCompatibility>,
795}
796impl CompatibilityMatrix {
797    pub fn new() -> Self {
798        Self {
799            matrix: HashMap::new(),
800        }
801    }
802    pub fn b(&mut self, module_a: &str, moduleb: &str, compatibility: ModuleCompatibility) {
803        self.matrix
804            .insert((module_a.to_string(), moduleb.to_string()), compatibility);
805    }
806    pub fn b_2(&self, module_a: &str, moduleb: &str) -> Option<&ModuleCompatibility> {
807        self.matrix
808            .get(&(module_a.to_string(), moduleb.to_string()))
809    }
810}
811impl Default for CompatibilityMatrix {
812    fn default() -> Self {
813        Self::new()
814    }
815}
816#[derive(Debug, Clone)]
817pub struct ModuleCompatibility {
818    compatible: bool,
819    reason: String,
820}
821impl ModuleCompatibility {
822    pub fn compatible() -> Self {
823        Self {
824            compatible: true,
825            reason: String::new(),
826        }
827    }
828    pub fn incompatible(reason: String) -> Self {
829        Self {
830            compatible: false,
831            reason,
832        }
833    }
834}
835impl ModuleCompatibility {
836    pub fn is_compatible(&self) -> bool {
837        self.compatible
838    }
839    pub fn reason_2(&self) -> &str {
840        &self.reason
841    }
842}
843/// Validation cache for performance optimization
844#[derive(Debug)]
845pub struct ValidationCache {
846    ecosystem_validation: Option<CachedValidationResult>,
847    module_validations: HashMap<String, CachedModuleValidation>,
848}
849impl ValidationCache {
850    pub fn new() -> Self {
851        Self {
852            ecosystem_validation: None,
853            module_validations: HashMap::new(),
854        }
855    }
856    pub fn cache_ecosystem_validation(&mut self, result: EcosystemValidationResult) {
857        self.ecosystem_validation = Some(CachedValidationResult {
858            result,
859            timestamp: Instant::now(),
860        });
861    }
862    pub fn get_ecosystem_validation(&self) -> Option<&CachedValidationResult> {
863        self.ecosystem_validation.as_ref()
864    }
865    pub fn invalidate_module_related_cache(&mut self) {
866        self.ecosystem_validation = None;
867        self.module_validations.clear();
868    }
869    pub fn clear(&mut self) {
870        self.ecosystem_validation = None;
871        self.module_validations.clear();
872    }
873}
874impl Default for ValidationCache {
875    fn default() -> Self {
876        Self::new()
877    }
878}
879#[derive(Debug, Clone)]
880pub struct CachedValidationResult {
881    pub result: EcosystemValidationResult,
882    pub timestamp: Instant,
883}
884impl CachedValidationResult {
885    pub fn age(&self, maxage: Duration) -> bool {
886        self.timestamp.elapsed() < maxage
887    }
888    pub fn is_recent(&self, maxage: Duration) -> bool {
889        self.age(maxage)
890    }
891}
892#[derive(Debug)]
893pub struct CachedModuleValidation {
894    pub result: ModuleValidationResult,
895    pub timestamp: Instant,
896}
897/// Validation policies and configuration
898#[derive(Debug, Clone)]
899pub struct ValidationPolicies {
900    pub enforce_semver: bool,
901    pub strict_version_matching: bool,
902    pub enforce_security_checks: bool,
903    pub allow_deprecated_apis: bool,
904    pub max_dependency_depth: usize,
905    pub incompatible_features: HashSet<String>,
906    pub required_features: HashSet<String>,
907}
908impl Default for ValidationPolicies {
909    fn default() -> Self {
910        Self {
911            enforce_semver: true,
912            strict_version_matching: false,
913            enforce_security_checks: true,
914            allow_deprecated_apis: true,
915            max_dependency_depth: 10,
916            incompatible_features: HashSet::new(),
917            required_features: HashSet::new(),
918        }
919    }
920}
921/// Comprehensive validation results
922#[derive(Debug, Clone)]
923pub struct EcosystemValidationResult {
924    pub timestamp: Instant,
925    pub validation_time: Duration,
926    pub moduleresults: HashMap<String, ModuleValidationResult>,
927    pub compatibilityresult: CompatibilityValidationResult,
928    pub api_stabilityresult: ApiStabilityResult,
929    pub version_consistencyresult: VersionConsistencyResult,
930    pub overall_status: ValidationStatus,
931}
932impl EcosystemValidationResult {
933    pub fn new() -> Self {
934        Self {
935            timestamp: Instant::now(),
936            validation_time: Duration::ZERO,
937            moduleresults: HashMap::new(),
938            compatibilityresult: CompatibilityValidationResult::new(),
939            api_stabilityresult: ApiStabilityResult::new(),
940            version_consistencyresult: VersionConsistencyResult::new(),
941            overall_status: ValidationStatus::Unknown,
942        }
943    }
944    pub fn name(&mut self, modulename: String, result: ModuleValidationResult) {
945        self.moduleresults.insert(modulename, result);
946        self.update_overall_status();
947    }
948    pub fn add_moduleresult(&mut self, modulename: String, result: ModuleValidationResult) {
949        self.moduleresults.insert(modulename, result);
950        self.update_overall_status();
951    }
952    pub fn add_compatibilityresult(&mut self, result: CompatibilityValidationResult) {
953        self.compatibilityresult = result;
954        self.update_overall_status();
955    }
956    pub fn add_api_stabilityresult(&mut self, result: ApiStabilityResult) {
957        self.api_stabilityresult = result;
958        self.update_overall_status();
959    }
960    pub fn add_version_consistencyresult(&mut self, result: VersionConsistencyResult) {
961        self.version_consistencyresult = result;
962        self.update_overall_status();
963    }
964    fn update_overall_status(&mut self) {
965        let haserrors = self.moduleresults.values().any(|r| !r.errors.is_empty())
966            || !self.compatibilityresult.incompatibilities.is_empty()
967            || !self.api_stabilityresult.breakingchanges.is_empty()
968            || !self.version_consistencyresult.conflicts.is_empty();
969        let has_warnings = self.moduleresults.values().any(|r| !r.warnings.is_empty());
970        self.overall_status = if haserrors {
971            ValidationStatus::Failed
972        } else if has_warnings {
973            ValidationStatus::Warning
974        } else {
975            ValidationStatus::Passed
976        };
977    }
978    pub fn is_valid(&self) -> bool {
979        matches!(
980            self.overall_status,
981            ValidationStatus::Passed | ValidationStatus::Warning
982        )
983    }
984    pub fn error_count(&self) -> usize {
985        self.moduleresults
986            .values()
987            .map(|r| r.errors.len())
988            .sum::<usize>()
989            + self.compatibilityresult.incompatibilities.len()
990            + self.api_stabilityresult.breakingchanges.len()
991            + self.version_consistencyresult.conflicts.len()
992    }
993    pub fn warning_count(&self) -> usize {
994        self.moduleresults.values().map(|r| r.warnings.len()).sum()
995    }
996}
997impl Default for EcosystemValidationResult {
998    fn default() -> Self {
999        Self::new()
1000    }
1001}
1002#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1003pub enum ValidationStatus {
1004    Unknown,
1005    Passed,
1006    Warning,
1007    Failed,
1008}
1009/// Individual module validation result
1010#[derive(Debug, Clone)]
1011pub struct ModuleValidationResult {
1012    pub modulename: String,
1013    pub errors: Vec<ValidationError>,
1014    pub warnings: Vec<ValidationWarning>,
1015    pub status: ValidationStatus,
1016}
1017impl ModuleValidationResult {
1018    pub fn new(modulename: String) -> Self {
1019        Self {
1020            modulename,
1021            errors: Vec::new(),
1022            warnings: Vec::new(),
1023            status: ValidationStatus::Unknown,
1024        }
1025    }
1026    pub fn adderror(&mut self, error: ValidationError) {
1027        self.errors.push(error);
1028        self.status = ValidationStatus::Failed;
1029    }
1030    pub fn add_warning(&mut self, warning: ValidationWarning) {
1031        self.warnings.push(warning);
1032        if self.status == ValidationStatus::Unknown {
1033            self.status = ValidationStatus::Warning;
1034        }
1035    }
1036    pub fn is_valid(&self) -> bool {
1037        self.errors.is_empty()
1038    }
1039}
1040#[derive(Debug, Clone)]
1041pub struct ValidationError {
1042    pub errortype: ValidationErrorType,
1043    pub message: String,
1044    pub context: Option<String>,
1045}
1046impl ValidationError {
1047    pub fn new(errortype: ValidationErrorType, message: String) -> Self {
1048        Self {
1049            errortype,
1050            message,
1051            context: None,
1052        }
1053    }
1054}
1055#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1056pub enum ValidationErrorType {
1057    InvalidVersion,
1058    DependencyError,
1059    ApiCompatibility,
1060    SecurityViolation,
1061    FeatureConflict,
1062}
1063#[derive(Debug, Clone)]
1064pub struct ValidationWarning {
1065    pub warningtype: ValidationWarningType,
1066    pub message: String,
1067}
1068impl ValidationWarning {
1069    pub fn new(warningtype: ValidationWarningType, message: String) -> Self {
1070        Self {
1071            warningtype,
1072            message,
1073        }
1074    }
1075}
1076#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1077pub enum ValidationWarningType {
1078    FeatureCompatibility,
1079    PerformanceImpact,
1080    DeprecationWarning,
1081}
1082/// Additional validation result types
1083#[derive(Debug, Clone)]
1084pub struct CompatibilityValidationResult {
1085    pub incompatibilities: Vec<String>,
1086}
1087impl CompatibilityValidationResult {
1088    pub fn new() -> Self {
1089        Self {
1090            incompatibilities: Vec::new(),
1091        }
1092    }
1093    pub fn add_incompatibility(&mut self, incompatibility: String) {
1094        self.incompatibilities.push(incompatibility);
1095    }
1096}
1097impl Default for CompatibilityValidationResult {
1098    fn default() -> Self {
1099        Self::new()
1100    }
1101}
1102#[derive(Debug, Clone)]
1103pub struct ApiStabilityResult {
1104    pub breakingchanges: HashMap<String, Vec<String>>,
1105    pub versioning_violations: HashMap<String, String>,
1106}
1107impl ApiStabilityResult {
1108    pub fn new() -> Self {
1109        Self {
1110            breakingchanges: HashMap::new(),
1111            versioning_violations: HashMap::new(),
1112        }
1113    }
1114    pub fn add_breaking_change(&mut self, module: String, changes: Vec<String>) {
1115        self.breakingchanges.insert(module, changes);
1116    }
1117    pub fn add_versioning_violation(&mut self, module: String, violation: String) {
1118        self.versioning_violations.insert(module, violation);
1119    }
1120}
1121impl Default for ApiStabilityResult {
1122    fn default() -> Self {
1123        Self::new()
1124    }
1125}
1126#[derive(Debug, Clone)]
1127pub struct VersionConsistencyResult {
1128    pub conflicts: HashMap<String, Vec<Version>>,
1129    pub dependency_mismatches: Vec<DependencyMismatch>,
1130}
1131impl VersionConsistencyResult {
1132    pub fn new() -> Self {
1133        Self {
1134            conflicts: HashMap::new(),
1135            dependency_mismatches: Vec::new(),
1136        }
1137    }
1138    pub fn add_conflict(&mut self, module: String, versions: Vec<Version>) {
1139        self.conflicts.insert(module, versions);
1140    }
1141    pub fn add_dependency_mismatch(
1142        &mut self,
1143        module: String,
1144        dependency: String,
1145        required: VersionRequirement,
1146        found: Version,
1147    ) {
1148        self.dependency_mismatches.push(DependencyMismatch {
1149            module,
1150            dependency,
1151            required,
1152            found,
1153        });
1154    }
1155}
1156impl Default for VersionConsistencyResult {
1157    fn default() -> Self {
1158        Self::new()
1159    }
1160}
1161#[derive(Debug, Clone)]
1162pub struct DependencyMismatch {
1163    pub module: String,
1164    pub dependency: String,
1165    pub required: VersionRequirement,
1166    pub found: Version,
1167}
1168/// Additional helper types
1169#[derive(Debug, Clone)]
1170pub struct DependencyValidationResult {
1171    pub dependency_name: String,
1172    pub incompatibilities: Vec<String>,
1173}
1174impl DependencyValidationResult {
1175    pub fn new(modulename: String) -> Self {
1176        Self {
1177            dependency_name: modulename,
1178            incompatibilities: Vec::new(),
1179        }
1180    }
1181    pub fn add_incompatibility(&mut self, incompatibility: String) {
1182        self.incompatibilities.push(incompatibility);
1183    }
1184    pub fn is_valid(&self) -> bool {
1185        self.incompatibilities.is_empty()
1186    }
1187}
1188#[derive(Debug, Clone)]
1189pub struct ApiValidationResult {
1190    pub documentation_issues: Vec<String>,
1191    pub semver_violations: Vec<String>,
1192    pub deprecation_issues: HashMap<String, String>,
1193}
1194impl ApiValidationResult {
1195    pub fn new() -> Self {
1196        Self {
1197            documentation_issues: Vec::new(),
1198            semver_violations: Vec::new(),
1199            deprecation_issues: HashMap::new(),
1200        }
1201    }
1202    pub fn name(&mut self, apiname: String) {
1203        self.documentation_issues.push(apiname);
1204    }
1205    pub fn add_documentation_issue(&mut self, apiname: String) {
1206        self.documentation_issues.push(apiname);
1207    }
1208    pub fn name_2(&mut self, apiname: String) {
1209        self.semver_violations.push(apiname);
1210    }
1211    pub fn add_semver_violation(&mut self, apiname: String) {
1212        self.semver_violations.push(apiname);
1213    }
1214    pub fn name_3(&mut self, apiname: String, issue: String) {
1215        self.deprecation_issues.insert(apiname, issue);
1216    }
1217    pub fn add_deprecation_issue(&mut self, apiname: String, issue: String) {
1218        self.deprecation_issues.insert(apiname, issue);
1219    }
1220    pub fn is_valid(&self) -> bool {
1221        self.documentation_issues.is_empty()
1222            && self.semver_violations.is_empty()
1223            && self.deprecation_issues.is_empty()
1224    }
1225}
1226impl Default for ApiValidationResult {
1227    fn default() -> Self {
1228        Self::new()
1229    }
1230}
1231#[derive(Debug, Clone)]
1232pub struct SecurityValidationResult {
1233    pub modulename: String,
1234    pub vulnerabilities: Vec<String>,
1235    pub security_issues: Vec<String>,
1236    /// Dependencies for which vulnerability status could not be determined
1237    /// (no advisory database is integrated into this build). These are
1238    /// *not* evidence of a vulnerability, only of an unperformed check; see
1239    /// [`Self::is_fully_verified`].
1240    pub unchecked_dependencies: Vec<String>,
1241}
1242impl SecurityValidationResult {
1243    pub fn new(modulename: String) -> Self {
1244        Self {
1245            modulename,
1246            vulnerabilities: Vec::new(),
1247            security_issues: Vec::new(),
1248            unchecked_dependencies: Vec::new(),
1249        }
1250    }
1251    pub fn add_vulnerability(&mut self, vulnerability: String) {
1252        self.vulnerabilities.push(vulnerability);
1253    }
1254    pub fn add_security_issue(&mut self, issue: String) {
1255        self.security_issues.push(issue);
1256    }
1257    pub fn add_unchecked_dependency(&mut self, dependency_name: String) {
1258        self.unchecked_dependencies.push(dependency_name);
1259    }
1260    /// `true` when no *confirmed* vulnerability or security issue was
1261    /// found. This does **not** mean every dependency was actually
1262    /// checked — see [`Self::is_fully_verified`] for that distinction.
1263    pub fn is_secure(&self) -> bool {
1264        self.vulnerabilities.is_empty() && self.security_issues.is_empty()
1265    }
1266    /// `true` only when every dependency's vulnerability status was
1267    /// actually determined (no [`Self::unchecked_dependencies`]). A module
1268    /// can be [`Self::is_secure`] (no confirmed problems) while *not* being
1269    /// fully verified (some dependencies were never checked at all).
1270    pub fn is_fully_verified(&self) -> bool {
1271        self.unchecked_dependencies.is_empty()
1272    }
1273}
1274#[derive(Debug, Clone)]
1275pub struct ApiStabilityCheck {
1276    pub is_stable: bool,
1277    pub breakingchanges: Vec<String>,
1278}
1279impl ApiStabilityCheck {
1280    pub fn new(is_stable: bool, breakingchanges: Vec<String>) -> Self {
1281        Self {
1282            is_stable,
1283            breakingchanges,
1284        }
1285    }
1286    pub fn is_stable(&self) -> bool {
1287        self.is_stable
1288    }
1289    pub fn breakingchanges(&self) -> &[String] {
1290        &self.breakingchanges
1291    }
1292    pub fn is_valid(&self) -> bool {
1293        self.is_stable && self.breakingchanges.is_empty()
1294    }
1295}
1296/// Ecosystem health summary
1297#[derive(Debug, Clone)]
1298pub struct EcosystemHealth {
1299    pub overall_status: HealthStatus,
1300    pub module_count: usize,
1301    pub error_count: usize,
1302    pub warning_count: usize,
1303    pub compatibility_score: f64,
1304    pub recommendations: Vec<String>,
1305}
1306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1307pub enum HealthStatus {
1308    Excellent,
1309    Good,
1310    Fair,
1311    Poor,
1312    Critical,
1313}
1314impl EcosystemHealth {
1315    pub fn from_validationresult(result: &EcosystemValidationResult) -> Self {
1316        let module_count = result.moduleresults.len();
1317        let error_count = result.error_count();
1318        let warning_count = result.warning_count();
1319        let compatibility_score = if module_count == 0 {
1320            1.0
1321        } else {
1322            1.0 - (error_count as f64 / (module_count as f64 * 10.0))
1323        };
1324        let overall_status = match error_count {
1325            0 => {
1326                if warning_count == 0 {
1327                    HealthStatus::Excellent
1328                } else {
1329                    HealthStatus::Good
1330                }
1331            }
1332            1..=5 => HealthStatus::Fair,
1333            6..=15 => HealthStatus::Poor,
1334            _ => HealthStatus::Critical,
1335        };
1336        let recommendations = Self::generate_recommendations(result);
1337        Self {
1338            overall_status,
1339            module_count,
1340            error_count,
1341            warning_count,
1342            compatibility_score: compatibility_score.clamp(0.0, 1.0),
1343            recommendations,
1344        }
1345    }
1346    fn generate_recommendations(result: &EcosystemValidationResult) -> Vec<String> {
1347        let mut recommendations = Vec::new();
1348        if result.error_count() > 0 {
1349            recommendations
1350                .push("Address validation errors before production deployment".to_string());
1351        }
1352        if !result.api_stabilityresult.breakingchanges.is_empty() {
1353            recommendations
1354                .push("Review API breaking changes and update version numbers".to_string());
1355        }
1356        if !result.version_consistencyresult.conflicts.is_empty() {
1357            recommendations.push("Resolve version conflicts between modules".to_string());
1358        }
1359        if result.warning_count() > 10 {
1360            recommendations
1361                .push("Consider addressing warnings to improve ecosystem stability".to_string());
1362        }
1363        recommendations
1364    }
1365}
1366/// Validate an API name follows the expected naming convention.
1367///
1368/// Functions (signatures starting with `fn `) must be snake_case.
1369/// Other items (types, traits, constants) must be CamelCase.
1370/// Returns an error message string if the name is invalid, or `None` if valid.
1371fn validate_api_name_format(name: &str, signature: &str) -> Option<String> {
1372    if name.is_empty() {
1373        return Some("API name must not be empty".to_string());
1374    }
1375    let t = signature.trim_start();
1376    let is_function = t.starts_with("fn ") || t.contains(" fn ");
1377    if is_function {
1378        let valid = name
1379            .chars()
1380            .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_');
1381        if !valid {
1382            return Some(format!(
1383                "Function '{}' must use snake_case naming convention",
1384                name
1385            ));
1386        }
1387    } else {
1388        let starts_upper = name.chars().next().is_some_and(|c| c.is_ascii_uppercase());
1389        let no_underscores = !name.contains('_');
1390        if !starts_upper || !no_underscores {
1391            return Some(format!(
1392                "Type/trait '{}' must use CamelCase naming convention",
1393                name
1394            ));
1395        }
1396    }
1397    None
1398}
1399/// Perform static API surface validation against the registered `ApiSurface`.
1400///
1401/// Checks performed:
1402/// 1. Each API entry must have a non-empty name and non-empty signature.
1403/// 2. Duplicate names within `public_apis` are flagged as breaking inconsistencies.
1404/// 3. Function names must be snake_case; type/trait names must be CamelCase.
1405/// 4. Stable APIs must carry a `since_version` and non-empty `documentation`.
1406/// 5. Deprecated API names must not collide with any current public API name.
1407///
1408/// An empty `public_apis` list is treated as valid (the module simply exposes no APIs).
1409pub fn validate_apisurface(apisurface: &ApiSurface) -> ApiStabilityCheck {
1410    let mut breaking_changes: Vec<String> = Vec::new();
1411    let public_names: HashSet<&str> = apisurface
1412        .public_apis
1413        .iter()
1414        .map(|a| a.name.as_str())
1415        .collect();
1416    let mut seen_names: HashSet<&str> = HashSet::new();
1417    for api in &apisurface.public_apis {
1418        if !seen_names.insert(api.name.as_str()) {
1419            breaking_changes.push(format!(
1420                "Duplicate API name '{}' found in public surface",
1421                api.name
1422            ));
1423        }
1424    }
1425    for api in &apisurface.public_apis {
1426        if api.name.is_empty() {
1427            breaking_changes.push("An API entry has an empty name".to_string());
1428        }
1429        if api.signature.is_empty() {
1430            breaking_changes.push(format!("API '{}' has an empty signature", api.name));
1431        }
1432        if let Some(msg) = validate_api_name_format(&api.name, &api.signature) {
1433            breaking_changes.push(msg);
1434        }
1435        if api.stability == ApiStability::Stable {
1436            if api.since_version.is_none() {
1437                breaking_changes.push(format!(
1438                    "Stable API '{}' is missing a 'since_version' annotation",
1439                    api.name
1440                ));
1441            }
1442            if api.documentation.is_empty() {
1443                breaking_changes.push(format!(
1444                    "Stable API '{}' is missing documentation",
1445                    api.name
1446                ));
1447            }
1448        }
1449    }
1450    for dep_api in &apisurface.deprecated_apis {
1451        if public_names.contains(dep_api.name.as_str()) {
1452            breaking_changes.push(format!(
1453                "Deprecated API '{}' collides with an active public API of the same name",
1454                dep_api.name
1455            ));
1456        }
1457    }
1458    let is_stable = breaking_changes.is_empty();
1459    ApiStabilityCheck::new(is_stable, breaking_changes)
1460}
1461/// Initialize ecosystem validation with detected modules
1462#[allow(dead_code)]
1463pub fn initialize_ecosystem_validation() -> CoreResult<()> {
1464    let validator = EcosystemValidator::global()?;
1465    validator.register_module(create_core_module_info())?;
1466    Ok(())
1467}
1468#[allow(dead_code)]
1469fn create_core_module_info() -> ModuleInfo {
1470    ModuleInfo {
1471        name: "scirs2-core".to_string(),
1472        version: "1.0.0".to_string(),
1473        dependencies: Vec::new(),
1474        apisurface: ApiSurface {
1475            public_apis: vec![ApiInfo {
1476                name: "validate_ecosystem".to_string(),
1477                signature: "fn validate_ecosystem() -> CoreResult<EcosystemValidationResult>"
1478                    .to_string(),
1479                documentation: "Validates the entire ecosystem compatibility".to_string(),
1480                since_version: Some(Version::new(1, 0, 0)),
1481                stability: ApiStability::Stable,
1482            }],
1483            deprecated_apis: Vec::new(),
1484        },
1485        features: vec!["validation".to_string(), "ecosystem".to_string()],
1486        metadata: ModuleMetadata {
1487            author: "SciRS2 Team".to_string(),
1488            description: "Core utilities for SciRS2 ecosystem".to_string(),
1489            license: "Apache-2.0".to_string(),
1490            repository: Some("https://github.com/cool-japan/scirs".to_string()),
1491            build_time: None,
1492        },
1493    }
1494}
1495
1496#[cfg(test)]
1497mod tests;