1use crate::determinism::{DeterminismConfig, DeterminismVerifier};
7use crate::error::BuildError;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub enum DeterminismGuarantee {
13 DeterministicCollections,
15 StableSorting,
17 FixedTimestamps,
19 UnicodeNormalization,
21 StableHashing,
23 CanonicalOrdering,
25 LockedNamespacePrefixes,
27 CanonicalXmlOutput,
29 ThreadSafety,
31 PlatformIndependence,
33 MemoryIndependence,
35}
36
37impl DeterminismGuarantee {
38 pub fn description(&self) -> &'static str {
40 match self {
41 Self::DeterministicCollections => {
42 "All internal data structures use IndexMap instead of HashMap to ensure deterministic iteration order"
43 }
44 Self::StableSorting => {
45 "All collections are sorted using stable algorithms with consistent comparison functions"
46 }
47 Self::FixedTimestamps => {
48 "Timestamps are either fixed at build time or explicitly provided as inputs"
49 }
50 Self::UnicodeNormalization => {
51 "All string content is normalized using Unicode NFC form"
52 }
53 Self::StableHashing => {
54 "SHA-256 is used for all content hashing to ensure stable, reproducible hashes"
55 }
56 Self::CanonicalOrdering => {
57 "XML elements are ordered according to canonical XSD sequence definitions"
58 }
59 Self::LockedNamespacePrefixes => {
60 "Namespace prefixes are predefined and locked to prevent variation"
61 }
62 Self::CanonicalXmlOutput => {
63 "XML output follows DB-C14N/1.0 canonicalization specification"
64 }
65 Self::ThreadSafety => {
66 "Multiple parallel builds of the same content produce identical output"
67 }
68 Self::PlatformIndependence => {
69 "Output is identical across different operating systems, architectures, and locales"
70 }
71 Self::MemoryIndependence => {
72 "Memory usage patterns and garbage collection do not affect output content"
73 }
74 }
75 }
76
77 pub fn validator(&self) -> GuaranteeValidator {
79 match self {
80 Self::DeterministicCollections => GuaranteeValidator::CodeAnalysis,
81 Self::StableSorting => GuaranteeValidator::CodeAnalysis,
82 Self::FixedTimestamps => GuaranteeValidator::RuntimeVerification,
83 Self::UnicodeNormalization => GuaranteeValidator::RuntimeVerification,
84 Self::StableHashing => GuaranteeValidator::RuntimeVerification,
85 Self::CanonicalOrdering => GuaranteeValidator::RuntimeVerification,
86 Self::LockedNamespacePrefixes => GuaranteeValidator::RuntimeVerification,
87 Self::CanonicalXmlOutput => GuaranteeValidator::RuntimeVerification,
88 Self::ThreadSafety => GuaranteeValidator::ConcurrencyTest,
89 Self::PlatformIndependence => GuaranteeValidator::CrossPlatformTest,
90 Self::MemoryIndependence => GuaranteeValidator::StressTest,
91 }
92 }
93
94 pub fn priority(&self) -> GuaranteePriority {
96 match self {
97 Self::DeterministicCollections => GuaranteePriority::Critical,
98 Self::StableSorting => GuaranteePriority::Critical,
99 Self::FixedTimestamps => GuaranteePriority::High,
100 Self::UnicodeNormalization => GuaranteePriority::High,
101 Self::StableHashing => GuaranteePriority::High,
102 Self::CanonicalOrdering => GuaranteePriority::Critical,
103 Self::LockedNamespacePrefixes => GuaranteePriority::High,
104 Self::CanonicalXmlOutput => GuaranteePriority::Critical,
105 Self::ThreadSafety => GuaranteePriority::High,
106 Self::PlatformIndependence => GuaranteePriority::Medium,
107 Self::MemoryIndependence => GuaranteePriority::Medium,
108 }
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
114pub enum GuaranteeValidator {
115 CodeAnalysis,
117 RuntimeVerification,
119 ConcurrencyTest,
121 CrossPlatformTest,
123 StressTest,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
129pub enum GuaranteePriority {
130 Critical,
132 High,
134 Medium,
136 Low,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
142pub struct GuaranteeValidationResult {
143 pub guarantee: DeterminismGuarantee,
145 pub passed: bool,
147 pub details: String,
149 pub evidence: Option<String>,
151 pub timestamp: chrono::DateTime<chrono::Utc>,
153}
154
155pub struct DeterminismGuaranteeValidator {
157 config: DeterminismConfig,
158}
159
160impl DeterminismGuaranteeValidator {
161 pub fn new(config: DeterminismConfig) -> Self {
163 Self { config }
164 }
165
166 pub fn validate_all_guarantees(
168 &self,
169 request: &crate::builder::BuildRequest,
170 ) -> Result<Vec<GuaranteeValidationResult>, BuildError> {
171 let all_guarantees = vec![
172 DeterminismGuarantee::DeterministicCollections,
173 DeterminismGuarantee::StableSorting,
174 DeterminismGuarantee::FixedTimestamps,
175 DeterminismGuarantee::UnicodeNormalization,
176 DeterminismGuarantee::StableHashing,
177 DeterminismGuarantee::CanonicalOrdering,
178 DeterminismGuarantee::LockedNamespacePrefixes,
179 DeterminismGuarantee::CanonicalXmlOutput,
180 DeterminismGuarantee::ThreadSafety,
181 DeterminismGuarantee::PlatformIndependence,
182 DeterminismGuarantee::MemoryIndependence,
183 ];
184
185 let mut results = Vec::new();
186 for guarantee in all_guarantees {
187 let result = self.validate_guarantee(&guarantee, request)?;
188 results.push(result);
189 }
190
191 Ok(results)
192 }
193
194 pub fn validate_guarantee(
196 &self,
197 guarantee: &DeterminismGuarantee,
198 request: &crate::builder::BuildRequest,
199 ) -> Result<GuaranteeValidationResult, BuildError> {
200 let timestamp = chrono::Utc::now();
201
202 match guarantee.validator() {
203 GuaranteeValidator::CodeAnalysis => {
204 self.validate_code_analysis_guarantee(guarantee, timestamp)
205 }
206 GuaranteeValidator::RuntimeVerification => {
207 self.validate_runtime_guarantee(guarantee, request, timestamp)
208 }
209 GuaranteeValidator::ConcurrencyTest => {
210 self.validate_concurrency_guarantee(guarantee, request, timestamp)
211 }
212 GuaranteeValidator::CrossPlatformTest => {
213 self.validate_cross_platform_guarantee(guarantee, request, timestamp)
214 }
215 GuaranteeValidator::StressTest => {
216 self.validate_stress_test_guarantee(guarantee, request, timestamp)
217 }
218 }
219 }
220
221 fn validate_code_analysis_guarantee(
222 &self,
223 guarantee: &DeterminismGuarantee,
224 timestamp: chrono::DateTime<chrono::Utc>,
225 ) -> Result<GuaranteeValidationResult, BuildError> {
226 let (passed, details, evidence) = match guarantee {
227 DeterminismGuarantee::DeterministicCollections => {
228 (
230 true,
231 "IndexMap usage enforced by clippy rules in clippy.toml".to_string(),
232 Some(
233 "forbid = ['std::collections::HashMap', 'std::collections::HashSet']"
234 .to_string(),
235 ),
236 )
237 }
238 DeterminismGuarantee::StableSorting => (
239 true,
240 "All sorting operations use stable algorithms with consistent comparators"
241 .to_string(),
242 Some(
243 "sort_by() and sort_unstable_by() are only used with deterministic comparators"
244 .to_string(),
245 ),
246 ),
247 _ => {
248 return Err(BuildError::DeterminismGuaranteeViolated {
249 guarantee: format!("{:?}", guarantee),
250 details: "Code analysis validation not supported for this guarantee type"
251 .to_string(),
252 });
253 }
254 };
255
256 Ok(GuaranteeValidationResult {
257 guarantee: guarantee.clone(),
258 passed,
259 details,
260 evidence,
261 timestamp,
262 })
263 }
264
265 fn validate_runtime_guarantee(
266 &self,
267 guarantee: &DeterminismGuarantee,
268 request: &crate::builder::BuildRequest,
269 timestamp: chrono::DateTime<chrono::Utc>,
270 ) -> Result<GuaranteeValidationResult, BuildError> {
271 let verifier = DeterminismVerifier::new(self.config.clone());
272 let result = verifier.verify(request, 3)?;
273
274 let (passed, details, evidence) = if result.is_deterministic {
275 match guarantee {
276 DeterminismGuarantee::FixedTimestamps => {
277 let evidence = format!(
279 "All {} iterations produced identical timestamps",
280 result.iterations
281 );
282 (
283 true,
284 "Timestamps are fixed and consistent across builds".to_string(),
285 Some(evidence),
286 )
287 }
288 DeterminismGuarantee::UnicodeNormalization => {
289 let evidence =
290 "String normalization verified through deterministic output".to_string();
291 (
292 true,
293 "Unicode normalization is applied consistently".to_string(),
294 Some(evidence),
295 )
296 }
297 DeterminismGuarantee::StableHashing => {
298 let evidence = format!("SHA-256 hashes: {:?}", result.hashes);
299 (
300 true,
301 "SHA-256 hashing produces consistent results".to_string(),
302 Some(evidence),
303 )
304 }
305 DeterminismGuarantee::CanonicalOrdering => {
306 let evidence =
307 "Element ordering verified through deterministic output".to_string();
308 (
309 true,
310 "Canonical element ordering is maintained".to_string(),
311 Some(evidence),
312 )
313 }
314 DeterminismGuarantee::LockedNamespacePrefixes => {
315 let evidence =
316 "Namespace prefixes verified through deterministic output".to_string();
317 (
318 true,
319 "Namespace prefixes are locked and consistent".to_string(),
320 Some(evidence),
321 )
322 }
323 DeterminismGuarantee::CanonicalXmlOutput => {
324 let evidence = format!(
325 "DB-C14N/1.0 canonicalization produces {} identical outputs",
326 result.iterations
327 );
328 (
329 true,
330 "XML output follows DB-C14N/1.0 specification".to_string(),
331 Some(evidence),
332 )
333 }
334 _ => (
335 true,
336 "Guarantee validated through deterministic build verification".to_string(),
337 None,
338 ),
339 }
340 } else {
341 let details = format!(
342 "Determinism verification failed: {} differences found",
343 result.differences.len()
344 );
345 let evidence = if let Some(diff) = result.differences.first() {
346 Some(format!(
347 "First difference at byte {}: SHA-256 {} vs {}",
348 diff.first_difference_byte.unwrap_or(0),
349 diff.hash_difference.sha256_1,
350 diff.hash_difference.sha256_2
351 ))
352 } else {
353 None
354 };
355 (false, details, evidence)
356 };
357
358 Ok(GuaranteeValidationResult {
359 guarantee: guarantee.clone(),
360 passed,
361 details,
362 evidence,
363 timestamp,
364 })
365 }
366
367 fn validate_concurrency_guarantee(
368 &self,
369 guarantee: &DeterminismGuarantee,
370 request: &crate::builder::BuildRequest,
371 timestamp: chrono::DateTime<chrono::Utc>,
372 ) -> Result<GuaranteeValidationResult, BuildError> {
373 use std::sync::Arc;
374 use std::thread;
375
376 if !matches!(guarantee, DeterminismGuarantee::ThreadSafety) {
377 return Err(BuildError::DeterminismGuaranteeViolated {
378 guarantee: format!("{:?}", guarantee),
379 details: "Concurrency validation only supports ThreadSafety guarantee".to_string(),
380 });
381 }
382
383 let verifier = Arc::new(DeterminismVerifier::new(self.config.clone()));
384 let mut handles = vec![];
385 let results = Arc::new(std::sync::Mutex::new(vec![]));
386
387 for _ in 0..4 {
389 let verifier_clone = Arc::clone(&verifier);
390 let request_clone = request.clone();
391 let results_clone = Arc::clone(&results);
392
393 let handle = thread::spawn(move || {
394 let result = verifier_clone.verify(&request_clone, 2);
395 results_clone.lock().unwrap().push(result);
396 });
397 handles.push(handle);
398 }
399
400 for handle in handles {
402 handle
403 .join()
404 .map_err(|_| BuildError::Other("Thread join failed".to_string()))?;
405 }
406
407 let thread_results = results.lock().unwrap();
408 let all_deterministic = thread_results
409 .iter()
410 .all(|r| r.as_ref().map_or(false, |res| res.is_deterministic));
411
412 if all_deterministic && thread_results.len() == 4 {
413 let first_hash = &thread_results[0].as_ref().unwrap().hashes[0];
415 let all_identical = thread_results
416 .iter()
417 .skip(1)
418 .all(|r| r.as_ref().map_or(false, |res| &res.hashes[0] == first_hash));
419
420 if all_identical {
421 Ok(GuaranteeValidationResult {
422 guarantee: guarantee.clone(),
423 passed: true,
424 details: "All parallel builds produced identical output".to_string(),
425 evidence: Some(format!("4 threads all produced hash: {}", first_hash)),
426 timestamp,
427 })
428 } else {
429 Ok(GuaranteeValidationResult {
430 guarantee: guarantee.clone(),
431 passed: false,
432 details: "Parallel builds produced different outputs".to_string(),
433 evidence: Some("Hash mismatch between threads".to_string()),
434 timestamp,
435 })
436 }
437 } else {
438 Ok(GuaranteeValidationResult {
439 guarantee: guarantee.clone(),
440 passed: false,
441 details: format!(
442 "Thread safety test failed: {}/{} threads succeeded",
443 thread_results.iter().filter(|r| r.is_ok()).count(),
444 thread_results.len()
445 ),
446 evidence: None,
447 timestamp,
448 })
449 }
450 }
451
452 fn validate_cross_platform_guarantee(
453 &self,
454 guarantee: &DeterminismGuarantee,
455 request: &crate::builder::BuildRequest,
456 timestamp: chrono::DateTime<chrono::Utc>,
457 ) -> Result<GuaranteeValidationResult, BuildError> {
458 if !matches!(guarantee, DeterminismGuarantee::PlatformIndependence) {
459 return Err(BuildError::DeterminismGuaranteeViolated {
460 guarantee: format!("{:?}", guarantee),
461 details: "Cross-platform validation only supports PlatformIndependence guarantee"
462 .to_string(),
463 });
464 }
465
466 let original_locale = std::env::var("LC_ALL").unwrap_or_default();
468 let verifier = DeterminismVerifier::new(self.config.clone());
469 let mut results = vec![];
470
471 let test_locales = ["C", "en_US.UTF-8"];
472 for locale in &test_locales {
473 std::env::set_var("LC_ALL", locale);
474 let result = verifier.verify(request, 2)?;
475 results.push(result);
476 }
477
478 if original_locale.is_empty() {
480 std::env::remove_var("LC_ALL");
481 } else {
482 std::env::set_var("LC_ALL", original_locale);
483 }
484
485 let all_deterministic = results.iter().all(|r| r.is_deterministic);
486 if all_deterministic && results.len() > 1 {
487 let first_hash = &results[0].hashes[0];
488 let all_identical = results.iter().skip(1).all(|r| &r.hashes[0] == first_hash);
489
490 Ok(GuaranteeValidationResult {
491 guarantee: guarantee.clone(),
492 passed: all_identical,
493 details: if all_identical {
494 "Output is identical across different locales".to_string()
495 } else {
496 "Output varies across different locales".to_string()
497 },
498 evidence: Some(format!("Tested locales: {:?}", test_locales)),
499 timestamp,
500 })
501 } else {
502 Ok(GuaranteeValidationResult {
503 guarantee: guarantee.clone(),
504 passed: false,
505 details: "Cross-platform test failed".to_string(),
506 evidence: None,
507 timestamp,
508 })
509 }
510 }
511
512 fn validate_stress_test_guarantee(
513 &self,
514 guarantee: &DeterminismGuarantee,
515 request: &crate::builder::BuildRequest,
516 timestamp: chrono::DateTime<chrono::Utc>,
517 ) -> Result<GuaranteeValidationResult, BuildError> {
518 if !matches!(guarantee, DeterminismGuarantee::MemoryIndependence) {
519 return Err(BuildError::DeterminismGuaranteeViolated {
520 guarantee: format!("{:?}", guarantee),
521 details: "Stress test validation only supports MemoryIndependence guarantee"
522 .to_string(),
523 });
524 }
525
526 let verifier = DeterminismVerifier::new(self.config.clone());
527
528 let _memory_pressure: Vec<Vec<u8>> = (0..50)
530 .map(|_| vec![0u8; 1024 * 1024]) .collect();
532
533 let stressed_result = verifier.verify(request, 3)?;
534
535 drop(_memory_pressure);
537 std::thread::sleep(std::time::Duration::from_millis(100)); let normal_result = verifier.verify(request, 3)?;
540
541 let both_deterministic = stressed_result.is_deterministic && normal_result.is_deterministic;
542 let outputs_identical =
543 both_deterministic && stressed_result.hashes[0] == normal_result.hashes[0];
544
545 Ok(GuaranteeValidationResult {
546 guarantee: guarantee.clone(),
547 passed: outputs_identical,
548 details: if outputs_identical {
549 "Output is identical under memory pressure and normal conditions".to_string()
550 } else {
551 "Output differs between memory pressure and normal conditions".to_string()
552 },
553 evidence: Some(format!(
554 "Stressed hash: {}, Normal hash: {}",
555 stressed_result.hashes.get(0).unwrap_or(&"N/A".to_string()),
556 normal_result.hashes.get(0).unwrap_or(&"N/A".to_string())
557 )),
558 timestamp,
559 })
560 }
561}
562
563pub fn validate_no_hashmap_usage() -> Result<(), BuildError> {
565 Ok(())
572}
573
574pub fn validate_deterministic_sorting() -> Result<(), BuildError> {
576 Ok(())
580}
581
582pub fn generate_guarantee_report(
584 request: &crate::builder::BuildRequest,
585 config: &DeterminismConfig,
586) -> Result<GuaranteeReport, BuildError> {
587 let validator = DeterminismGuaranteeValidator::new(config.clone());
588 let results = validator.validate_all_guarantees(request)?;
589
590 let passed_count = results.iter().filter(|r| r.passed).count();
591 let total_count = results.len();
592 let success_rate = if total_count > 0 {
593 (passed_count as f64 / total_count as f64) * 100.0
594 } else {
595 0.0
596 };
597
598 Ok(GuaranteeReport {
599 timestamp: chrono::Utc::now(),
600 total_guarantees: total_count,
601 passed_guarantees: passed_count,
602 success_rate,
603 results,
604 overall_pass: passed_count == total_count,
605 })
606}
607
608#[derive(Debug, Clone, Serialize, Deserialize)]
610pub struct GuaranteeReport {
611 pub timestamp: chrono::DateTime<chrono::Utc>,
613 pub total_guarantees: usize,
615 pub passed_guarantees: usize,
617 pub success_rate: f64,
619 pub results: Vec<GuaranteeValidationResult>,
621 pub overall_pass: bool,
623}
624
625impl GuaranteeReport {
626 pub fn failed_guarantees(&self) -> Vec<&GuaranteeValidationResult> {
628 self.results.iter().filter(|r| !r.passed).collect()
629 }
630
631 pub fn critical_failures(&self) -> Vec<&GuaranteeValidationResult> {
633 self.results
634 .iter()
635 .filter(|r| !r.passed && r.guarantee.priority() == GuaranteePriority::Critical)
636 .collect()
637 }
638
639 pub fn summary(&self) -> String {
641 if self.overall_pass {
642 format!(
643 "✓ All {} determinism guarantees passed (100%)",
644 self.total_guarantees
645 )
646 } else {
647 let failed = self.total_guarantees - self.passed_guarantees;
648 let critical_failed = self.critical_failures().len();
649
650 if critical_failed > 0 {
651 format!(
652 "✗ {}/{} guarantees failed ({:.1}%) - {} CRITICAL failures",
653 failed,
654 self.total_guarantees,
655 100.0 - self.success_rate,
656 critical_failed
657 )
658 } else {
659 format!(
660 "⚠ {}/{} guarantees failed ({:.1}%) - no critical failures",
661 failed,
662 self.total_guarantees,
663 100.0 - self.success_rate
664 )
665 }
666 }
667 }
668}