Skip to main content

feagi_evolutionary/genome/validators/
mod.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Per-version genome validators.
5//!
6//! A `Validator` checks structural integrity, parameter ranges, and
7//! cross-references at a *specific* `GenomeSchemaVersion`. The chain runner
8//! invokes per-version validators between hops as **advisory** and the
9//! validator at the final target version as **blocking**.
10//!
11//! See `feagi-core/docs/GENOME_SCHEMA_VERSIONING.md` and
12//! `crates/feagi-evolutionary/src/genome/README.md` for the design contract.
13//!
14//! Validators MUST NOT mutate the genome. Mutation belongs in
15//! `crate::genome::migration::Migrator`. See the README's anti-patterns.
16
17use serde_json::Value;
18
19use crate::genome::schema::GenomeSchemaVersion;
20
21pub mod v3;
22
23pub use v3::V3Validator;
24
25/// Outcome of running a single validator against a genome.
26///
27/// `errors` are blocking issues. `warnings` are advisory. The validator does
28/// not decide whether to abort the chain; the chain runner makes that call
29/// based on whether the validator was the latest (blocking) or intermediate
30/// (advisory).
31#[derive(Debug, Clone, Default)]
32pub struct ValidationReport {
33    /// Schema version this report describes. `None` only when constructed
34    /// via `Default` for testing scaffolding; production validators always
35    /// stamp their version.
36    pub schema_version: Option<GenomeSchemaVersion>,
37    pub errors: Vec<String>,
38    pub warnings: Vec<String>,
39}
40
41impl ValidationReport {
42    pub fn new(schema_version: GenomeSchemaVersion) -> Self {
43        Self {
44            schema_version: Some(schema_version),
45            errors: Vec::new(),
46            warnings: Vec::new(),
47        }
48    }
49
50    pub fn add_error(&mut self, msg: impl Into<String>) {
51        self.errors.push(msg.into());
52    }
53
54    pub fn add_warning(&mut self, msg: impl Into<String>) {
55        self.warnings.push(msg.into());
56    }
57
58    /// True when the report carries at least one blocking error.
59    pub fn has_errors(&self) -> bool {
60        !self.errors.is_empty()
61    }
62
63    /// True when the report carries no errors and no warnings.
64    pub fn is_clean(&self) -> bool {
65        self.errors.is_empty() && self.warnings.is_empty()
66    }
67}
68
69/// Validates a genome at a specific schema version.
70///
71/// Validators are stateless; the trait is `Send + Sync` so registries can be
72/// shared across threads. Implementations must not mutate the input or
73/// perform I/O.
74pub trait Validator: Send + Sync {
75    /// The schema version this validator targets.
76    fn schema_version(&self) -> GenomeSchemaVersion;
77
78    /// Inspect a genome and return findings. Never mutates.
79    fn validate(&self, genome: &Value) -> ValidationReport;
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn report_starts_clean() {
88        let r = ValidationReport::new(GenomeSchemaVersion(3));
89        assert!(r.is_clean());
90        assert!(!r.has_errors());
91        assert_eq!(r.schema_version, Some(GenomeSchemaVersion(3)));
92    }
93
94    #[test]
95    fn add_error_breaks_clean_and_blocks() {
96        let mut r = ValidationReport::new(GenomeSchemaVersion(3));
97        r.add_error("missing field");
98        assert!(r.has_errors());
99        assert!(!r.is_clean());
100        assert_eq!(r.errors, vec!["missing field".to_string()]);
101    }
102
103    #[test]
104    fn warning_is_advisory_only() {
105        let mut r = ValidationReport::new(GenomeSchemaVersion(3));
106        r.add_warning("nudge");
107        assert!(!r.has_errors());
108        assert!(!r.is_clean());
109        assert_eq!(r.warnings, vec!["nudge".to_string()]);
110    }
111}