clnrm_core/validation/
orchestrator.rs1use crate::error::{CleanroomError, Result};
6use crate::validation::count_validator::CountExpectation;
7use crate::validation::graph_validator::GraphExpectation;
8use crate::validation::hermeticity_validator::HermeticityExpectation;
9use crate::validation::span_validator::SpanData;
10use crate::validation::window_validator::WindowExpectation;
11
12#[derive(Debug, Clone, Default)]
14pub struct PrdExpectations {
15 pub graph: Option<GraphExpectation>,
17 pub counts: Option<CountExpectation>,
19 pub windows: Vec<WindowExpectation>,
21 pub hermeticity: Option<HermeticityExpectation>,
23}
24
25impl PrdExpectations {
26 pub fn new() -> Self {
28 Self::default()
29 }
30
31 pub fn with_graph(mut self, graph: GraphExpectation) -> Self {
33 self.graph = Some(graph);
34 self
35 }
36
37 pub fn with_counts(mut self, counts: CountExpectation) -> Self {
39 self.counts = Some(counts);
40 self
41 }
42
43 pub fn add_window(mut self, window: WindowExpectation) -> Self {
45 self.windows.push(window);
46 self
47 }
48
49 pub fn with_hermeticity(mut self, hermeticity: HermeticityExpectation) -> Self {
51 self.hermeticity = Some(hermeticity);
52 self
53 }
54
55 pub fn validate_all(&self, spans: &[SpanData]) -> Result<ValidationReport> {
69 let mut report = ValidationReport::new();
70
71 if let Some(ref graph) = self.graph {
73 match graph.validate(spans) {
74 Ok(_) => report.add_pass("graph_topology"),
75 Err(e) => report.add_fail("graph_topology", e.to_string()),
76 }
77 }
78
79 if let Some(ref counts) = self.counts {
81 match counts.validate(spans) {
82 Ok(_) => report.add_pass("span_counts"),
83 Err(e) => report.add_fail("span_counts", e.to_string()),
84 }
85 }
86
87 for (idx, window) in self.windows.iter().enumerate() {
89 let name = format!("window_{}_outer_{}", idx, window.outer);
90 match window.validate(spans) {
91 Ok(_) => report.add_pass(&name),
92 Err(e) => report.add_fail(&name, e.to_string()),
93 }
94 }
95
96 if let Some(ref hermetic) = self.hermeticity {
98 match hermetic.validate(spans) {
99 Ok(_) => report.add_pass("hermeticity"),
100 Err(e) => report.add_fail("hermeticity", e.to_string()),
101 }
102 }
103
104 Ok(report)
105 }
106
107 pub fn validate_strict(&self, spans: &[SpanData]) -> Result<()> {
109 let report = self.validate_all(spans)?;
110 if report.is_success() {
111 Ok(())
112 } else {
113 Err(CleanroomError::validation_error(format!(
114 "Validation failed with {} errors: {}",
115 report.failure_count(),
116 report.first_error().unwrap_or("unknown error")
117 )))
118 }
119 }
120}
121
122#[derive(Debug, Clone, Default)]
124pub struct ValidationReport {
125 passes: Vec<String>,
127 failures: Vec<(String, String)>,
129}
130
131impl ValidationReport {
132 pub fn new() -> Self {
134 Self::default()
135 }
136
137 pub fn add_pass(&mut self, name: &str) {
139 self.passes.push(name.to_string());
140 }
141
142 pub fn add_fail(&mut self, name: &str, error: String) {
144 self.failures.push((name.to_string(), error));
145 }
146
147 pub fn is_success(&self) -> bool {
149 self.failures.is_empty()
150 }
151
152 pub fn pass_count(&self) -> usize {
154 self.passes.len()
155 }
156
157 pub fn failure_count(&self) -> usize {
159 self.failures.len()
160 }
161
162 pub fn passes(&self) -> &[String] {
164 &self.passes
165 }
166
167 pub fn failures(&self) -> &[(String, String)] {
169 &self.failures
170 }
171
172 pub fn first_error(&self) -> Option<&str> {
174 self.failures.first().map(|(_, msg)| msg.as_str())
175 }
176
177 pub fn summary(&self) -> String {
179 if self.is_success() {
180 format!("✓ All {} validations passed", self.pass_count())
181 } else {
182 format!(
183 "✗ {} passed, {} failed\n{}",
184 self.pass_count(),
185 self.failure_count(),
186 self.failures
187 .iter()
188 .map(|(name, err)| format!(" - {}: {}", name, err))
189 .collect::<Vec<_>>()
190 .join("\n")
191 )
192 }
193 }
194}