agileplus-trace-validator 0.3.0

Typed intent graphs for engineering work — convert prompts into validated DAGs of intents, features, tasks, tests, and edges, then store, query, and serve them from Rust, HTTP, or MCP.
Documentation
//! Node-level validation: completeness checks, ID format, required fields.

use crate::{ValidationError, ValidationResult};
use agileplus_domain::intent_graph::*;
use regex::Regex;

/// Validate that every node has required fields, correct ID format, and valid status.
pub fn validate_node_completeness(graph: &IntentGraph) -> ValidationResult {
    let mut errors = Vec::new();
    let re = Regex::new(r"^[A-Z][a-z]+#[a-z0-9\-]+$").unwrap();

    for node in &graph.nodes {
        // Required ID format
        if !re.is_match(&node.id.0) {
            errors.push(ValidationError::InvalidNodeId(format!(
                "Node ID '{}' does not match required pattern ^[A-Z][a-z]+#[a-z0-9\\-]+$",
                node.id
            )));
        }

        // Required fields
        if node.title.trim().is_empty() {
            errors.push(ValidationError::MissingRequiredField(format!(
                "Node {} is missing title",
                node.id
            )));
        }

        if node.meta.source.trim().is_empty() {
            errors.push(ValidationError::MissingRequiredField(format!(
                "Node {} meta.source is empty",
                node.id
            )));
        }

        if node.meta.timestamp.trim().is_empty() {
            errors.push(ValidationError::MissingRequiredField(format!(
                "Node {} meta.timestamp is empty",
                node.id
            )));
        }

        // Confidence range check
        if let Some(c) = node.meta.confidence {
            if !(0.0..=1.0).contains(&c) {
                errors.push(ValidationError::MissingRequiredField(format!(
                    "Node {} meta.confidence {} is out of range [0.0, 1.0]",
                    node.id, c
                )));
            }
        }
    }

    if errors.is_empty() {
        Ok(())
    } else {
        Err(errors)
    }
}

/// Validate only node IDs without full graph traversal. Fast check.
pub fn validate_node_ids_only(graph: &IntentGraph) -> ValidationResult {
    let mut errors = Vec::new();
    let re = Regex::new(r"^[A-Z][a-z]+#[a-z0-9\-]+$").unwrap();

    for node in &graph.nodes {
        if !re.is_match(&node.id.0) {
            errors.push(ValidationError::InvalidNodeId(format!(
                "Node ID '{}' does not match required pattern",
                node.id
            )));
        }
    }

    if errors.is_empty() {
        Ok(())
    } else {
        Err(errors)
    }
}