//! Phase 43 Validation Pipeline. //! //! PRD: "Staffing outputs run through schema / completeness / //! consistency / policy gates. Plug into Layer 5 execution loop — //! failure triggers observer-correction iteration." //! //! This crate provides the `Validator` trait + `Artifact` enum + //! Report/ValidationError types. Staffing validators (fill, email, //! playbook) and the DevOps scaffold live in submodules. //! //! Landed 2026-04-24 as a scaffold — the trait + types + module //! layout match the PRD; individual validator implementations are //! `Unimplemented` stubs that return a clear "phase 43 not wired" //! error rather than silently passing. The execution-loop integration //! (generate → validate → correct → retry) comes in a follow-up //! commit once the stubs are filled. use serde::{Deserialize, Serialize}; use thiserror::Error; pub mod staffing; pub mod devops; /// What a validator saw. One variant per artifact class we validate. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind")] pub enum Artifact { /// A fill proposal from the staffing executor — shape is /// `{fills: [{candidate_id, name}]}` per PRD. FillProposal(serde_json::Value), /// An email/SMS draft for outreach. EmailDraft(serde_json::Value), /// A playbook being sealed for memory. Playbook(serde_json::Value), /// Terraform plan output (scaffold, long-horizon). TerraformPlan(serde_json::Value), /// Ansible playbook (scaffold, long-horizon). AnsiblePlaybook(serde_json::Value), } /// Success report. Empty `findings` means a clean pass. Populated /// findings with `Severity::Warning` means "acceptable but notable" — /// the artifact passes. `Severity::Error` means validation failed; /// the validator should return `Err(...)` in that case, not `Ok`. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Report { pub findings: Vec, pub elapsed_ms: u64, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Finding { pub field: String, pub severity: Severity, pub message: String, } #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum Severity { Warning, Error, } /// Validation failure — what went wrong + where + why. Returned as /// `Err` from `validate`. Execution loop catches these and feeds them /// to the observer-correction retry loop. #[derive(Debug, Clone, Error, Serialize, Deserialize)] pub enum ValidationError { /// Artifact schema doesn't match what we expected. #[error("schema mismatch at {field}: {reason}")] Schema { field: String, reason: String }, /// Required data missing (e.g. endorsed count != target count). #[error("completeness: {reason}")] Completeness { reason: String }, /// Data that's inconsistent with another source of truth /// (e.g. worker_id doesn't exist in the workers table). #[error("consistency: {reason}")] Consistency { reason: String }, /// Policy violation — truth rule or access control said no. #[error("policy: {reason}")] Policy { reason: String }, /// Validator hasn't been implemented yet — scaffold stub. #[error("validator not yet implemented for {artifact} — phase 43 scaffold")] Unimplemented { artifact: &'static str }, } /// Core validation contract. Implementations live in `staffing::*` and /// `devops::*`. The execution loop dispatches to the right impl based /// on the Artifact variant. pub trait Validator: Send + Sync { fn validate(&self, artifact: &Artifact) -> Result; /// Human-readable name for logs + Langfuse traces. fn name(&self) -> &'static str; }