allow_report/
diff_human.rs1use crate::diff_policy_detail::policy_change_detail;
2use crate::diff_posture::{diff_net_posture, diff_posture_summary};
3use crate::{DiffFindingChange, DiffPolicyChange};
4
5const DIFF_HUMAN_CHANGE_LIMIT: usize = 120;
6
7pub fn render_diff_posture_summary_human(
8 current_failures: usize,
9 finding_changes: &[DiffFindingChange<'_>],
10 policy_changes: &[DiffPolicyChange<'_>],
11) -> String {
12 render_diff_posture_summary_human_with_evidence_health(
13 current_failures,
14 0,
15 0,
16 finding_changes,
17 policy_changes,
18 )
19}
20
21pub fn render_diff_posture_summary_human_with_evidence_health(
22 current_failures: usize,
23 broken_evidence_links: usize,
24 weak_evidence_references: usize,
25 finding_changes: &[DiffFindingChange<'_>],
26 policy_changes: &[DiffPolicyChange<'_>],
27) -> String {
28 let summary = diff_posture_summary(current_failures, finding_changes, policy_changes);
29 let posture = diff_net_posture(summary);
30 let mut out = String::new();
31 out.push_str("\nDiff posture summary:\n");
32 out.push_str(&format!(" net_posture: {}\n", posture.as_str()));
33 out.push_str(&format!(
34 " reviewer_action: {}\n",
35 posture.reviewer_action()
36 ));
37 out.push_str(&format!(
38 " current_check_failures: {}\n",
39 summary.current_failures
40 ));
41 if broken_evidence_links > 0 {
42 out.push_str(&format!(
43 " broken_evidence_links: {broken_evidence_links}\n"
44 ));
45 }
46 if weak_evidence_references > 0 {
47 out.push_str(&format!(
48 " weak_evidence_references: {weak_evidence_references}\n"
49 ));
50 }
51 if broken_evidence_links > 0 || weak_evidence_references > 0 {
52 out.push_str(" evidence_repair_queues:\n");
53 if broken_evidence_links > 0 {
54 out.push_str(
55 " cargo-allow worklist --item-kind broken_evidence_link --format json\n",
56 );
57 }
58 if weak_evidence_references > 0 {
59 out.push_str(
60 " cargo-allow worklist --item-kind weak_evidence_reference --format json\n",
61 );
62 }
63 }
64 out.push_str(&format!(
65 " new_source_findings: {}\n",
66 summary.new_findings
67 ));
68 out.push_str(&format!(
69 " removed_source_findings: {}\n",
70 summary.removed_findings
71 ));
72 out.push_str(&format!(" policy_failures: {}\n", summary.policy_failures));
73 out.push_str(&format!(
74 " policy_review_items: {}\n",
75 summary.policy_review_items
76 ));
77 out.push_str(&format!(
78 " policy_improvements: {}\n",
79 summary.policy_improvements
80 ));
81 out
82}
83
84pub fn render_diff_finding_changes_human(changes: &[DiffFindingChange<'_>]) -> String {
85 let mut out = String::new();
86 out.push_str("\nFinding posture changes:\n");
87 if changes.is_empty() {
88 out.push_str(" none\n");
89 return out;
90 }
91 append_finding_changes_human_section(&mut out, "Finding attention", changes, "new");
92 append_finding_changes_human_section(&mut out, "Finding improvements", changes, "removed");
93 let known_changes = ["new", "removed"];
94 if changes
95 .iter()
96 .any(|change| !known_changes.contains(&change.change))
97 {
98 out.push_str(" Other finding changes:\n");
99 let other_count = changes
100 .iter()
101 .filter(|change| !known_changes.contains(&change.change))
102 .count();
103 for change in changes
104 .iter()
105 .filter(|change| !known_changes.contains(&change.change))
106 .take(DIFF_HUMAN_CHANGE_LIMIT)
107 {
108 append_finding_change_human_row(&mut out, change);
109 }
110 if other_count > DIFF_HUMAN_CHANGE_LIMIT {
111 out.push_str(&format!(
112 " ... {} more omitted\n",
113 other_count - DIFF_HUMAN_CHANGE_LIMIT
114 ));
115 }
116 }
117 out
118}
119
120fn append_finding_changes_human_section(
121 out: &mut String,
122 heading: &str,
123 changes: &[DiffFindingChange<'_>],
124 change_kind: &str,
125) {
126 if !changes.iter().any(|change| change.change == change_kind) {
127 return;
128 }
129 out.push_str(&format!(" {heading}:\n"));
130 let matching_count = changes
131 .iter()
132 .filter(|change| change.change == change_kind)
133 .count();
134 for change in changes
135 .iter()
136 .filter(|change| change.change == change_kind)
137 .take(DIFF_HUMAN_CHANGE_LIMIT)
138 {
139 append_finding_change_human_row(out, change);
140 }
141 if matching_count > DIFF_HUMAN_CHANGE_LIMIT {
142 out.push_str(&format!(
143 " ... {} more omitted\n",
144 matching_count - DIFF_HUMAN_CHANGE_LIMIT
145 ));
146 }
147}
148
149fn append_finding_change_human_row(out: &mut String, change: &DiffFindingChange<'_>) {
150 out.push_str(&format!(
151 " {} {}{} at {}\n",
152 change.change,
153 change.kind,
154 change
155 .family
156 .map(|family| format!(".{family}"))
157 .unwrap_or_default(),
158 change.path
159 ));
160}
161
162pub fn render_diff_policy_changes_human(changes: &[DiffPolicyChange<'_>]) -> String {
163 let mut out = String::new();
164 out.push_str("\nPolicy posture changes:\n");
165 if changes.is_empty() {
166 out.push_str(" none\n");
167 return out;
168 }
169 append_policy_changes_human_section(&mut out, "Policy failures", changes, "fail");
170 append_policy_changes_human_section(&mut out, "Policy review required", changes, "review");
171 append_policy_changes_human_section(&mut out, "Policy improvements", changes, "improvement");
172 let known_severities = ["fail", "review", "improvement"];
173 if changes
174 .iter()
175 .any(|change| !known_severities.contains(&change.severity))
176 {
177 out.push_str(" Other policy changes:\n");
178 for change in changes
179 .iter()
180 .filter(|change| !known_severities.contains(&change.severity))
181 {
182 append_policy_change_human_row(&mut out, change);
183 }
184 }
185 out
186}
187
188fn append_policy_changes_human_section(
189 out: &mut String,
190 heading: &str,
191 changes: &[DiffPolicyChange<'_>],
192 severity: &str,
193) {
194 if !changes.iter().any(|change| change.severity == severity) {
195 return;
196 }
197 out.push_str(&format!(" {heading}:\n"));
198 for change in changes {
199 if change.severity == severity {
200 append_policy_change_human_row(out, change);
201 }
202 }
203}
204
205fn append_policy_change_human_row(out: &mut String, change: &DiffPolicyChange<'_>) {
206 out.push_str(&format!(
207 " {} {} {}: {}\n",
208 change.severity, change.allow_id, change.kind, change.message
209 ));
210 if let Some(detail) = policy_change_detail(change) {
211 out.push_str(&format!(" detail: {detail}\n"));
212 }
213}