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;
22pub mod v4;
23
24pub use v3::V3Validator;
25pub use v4::V4Validator;
26
27/// Outcome of running a single validator against a genome.
28///
29/// `errors` are blocking issues. `warnings` are advisory. The validator does
30/// not decide whether to abort the chain; the chain runner makes that call
31/// based on whether the validator was the latest (blocking) or intermediate
32/// (advisory).
33#[derive(Debug, Clone, Default)]
34pub struct ValidationReport {
35    /// Schema version this report describes. `None` only when constructed
36    /// via `Default` for testing scaffolding; production validators always
37    /// stamp their version.
38    pub schema_version: Option<GenomeSchemaVersion>,
39    pub errors: Vec<String>,
40    pub warnings: Vec<String>,
41}
42
43impl ValidationReport {
44    pub fn new(schema_version: GenomeSchemaVersion) -> Self {
45        Self {
46            schema_version: Some(schema_version),
47            errors: Vec::new(),
48            warnings: Vec::new(),
49        }
50    }
51
52    pub fn add_error(&mut self, msg: impl Into<String>) {
53        self.errors.push(msg.into());
54    }
55
56    pub fn add_warning(&mut self, msg: impl Into<String>) {
57        self.warnings.push(msg.into());
58    }
59
60    /// True when the report carries at least one blocking error.
61    pub fn has_errors(&self) -> bool {
62        !self.errors.is_empty()
63    }
64
65    /// True when the report carries no errors and no warnings.
66    pub fn is_clean(&self) -> bool {
67        self.errors.is_empty() && self.warnings.is_empty()
68    }
69}
70
71/// Validates a genome at a specific schema version.
72///
73/// Validators are stateless; the trait is `Send + Sync` so registries can be
74/// shared across threads. Implementations must not mutate the input or
75/// perform I/O.
76pub trait Validator: Send + Sync {
77    /// The schema version this validator targets.
78    fn schema_version(&self) -> GenomeSchemaVersion;
79
80    /// Inspect a genome and return findings. Never mutates.
81    fn validate(&self, genome: &Value) -> ValidationReport;
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn report_starts_clean() {
90        let r = ValidationReport::new(GenomeSchemaVersion(3));
91        assert!(r.is_clean());
92        assert!(!r.has_errors());
93        assert_eq!(r.schema_version, Some(GenomeSchemaVersion(3)));
94    }
95
96    #[test]
97    fn add_error_breaks_clean_and_blocks() {
98        let mut r = ValidationReport::new(GenomeSchemaVersion(3));
99        r.add_error("missing field");
100        assert!(r.has_errors());
101        assert!(!r.is_clean());
102        assert_eq!(r.errors, vec!["missing field".to_string()]);
103    }
104
105    #[test]
106    fn warning_is_advisory_only() {
107        let mut r = ValidationReport::new(GenomeSchemaVersion(3));
108        r.add_warning("nudge");
109        assert!(!r.has_errors());
110        assert!(!r.is_clean());
111        assert_eq!(r.warnings, vec!["nudge".to_string()]);
112    }
113}