Skip to main content

ward/engine/
verifier.rs

1use anyhow::Result;
2
3use crate::config::SecurityConfig;
4use crate::github::Client;
5
6use super::planner::{self, RepoPlan};
7
8/// Re-read security state and verify it matches the plan.
9pub async fn verify_security(
10    client: &Client,
11    plans: &[RepoPlan],
12    desired: &SecurityConfig,
13) -> Result<VerificationReport> {
14    let mut report = VerificationReport::default();
15
16    for plan in plans.iter().filter(|p| p.has_changes()) {
17        let current = client.get_security_state(&plan.repo).await?;
18        let re_plan = planner::plan_security(&plan.repo, &current, desired);
19
20        if re_plan.has_changes() {
21            report.mismatches.push(VerificationMismatch {
22                repo: plan.repo.clone(),
23                remaining_changes: re_plan
24                    .changes
25                    .iter()
26                    .map(|c| format!("{}: {} (expected {})", c.feature, c.current, c.desired))
27                    .collect(),
28            });
29        } else {
30            report.verified += 1;
31        }
32    }
33
34    Ok(report)
35}
36
37#[derive(Debug, Default)]
38pub struct VerificationReport {
39    pub verified: usize,
40    pub mismatches: Vec<VerificationMismatch>,
41}
42
43#[derive(Debug)]
44pub struct VerificationMismatch {
45    pub repo: String,
46    pub remaining_changes: Vec<String>,
47}
48
49impl VerificationReport {
50    pub fn print_summary(&self) {
51        use console::style;
52
53        println!();
54        if self.mismatches.is_empty() {
55            println!(
56                "  {} Verification passed: all {} repos match desired state.",
57                style("✅").green(),
58                self.verified
59            );
60        } else {
61            println!(
62                "  {} Verification: {} passed, {} mismatches:",
63                style("⚠️").yellow(),
64                self.verified,
65                self.mismatches.len()
66            );
67            for m in &self.mismatches {
68                println!("    {} {}:", style("❌").red(), m.repo);
69                for change in &m.remaining_changes {
70                    println!("      - {change}");
71                }
72            }
73        }
74    }
75}