1use anyhow::Result;
2
3use crate::config::SecurityConfig;
4use crate::github::Client;
5
6use super::planner::{self, RepoPlan};
7
8pub 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, ¤t, 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}