1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use crate::{Decision, DecisiveClause, DenyShape, Presence};
/// Who may receive an explanation. Audit output contains internal fact names.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExplanationAudience {
/// Minimal response, including generic text for hidden denials.
Public,
/// Internal inspection of the recorded evaluation, without provider reruns.
Audit,
}
impl<O> Decision<O> {
/// Formats the observed decision for an explicitly selected audience.
///
/// Audit explanations reveal stable check names and boolean observations.
/// They never capture Debug output from the principal, resource or outcome.
/// Unconsulted clauses are not reconstructed from a trace or reevaluated.
#[must_use]
pub fn explain(&self, audience: ExplanationAudience) -> String {
let public = match &self.trace.decisive {
DecisiveClause::Deny {
shape: DenyShape::Hidden,
..
} => "not found",
_ if self.is_permit() => "permitted",
_ => "denied",
};
if audience == ExplanationAudience::Public {
return public.to_owned();
}
let mut output = format!(
"{}\n",
if self.is_permit() {
"permitted"
} else {
"denied"
}
);
for (fact, presence) in &self.trace.consulted {
let value = match presence {
Presence::Present => "true",
Presence::Absent => "false",
Presence::Unknown => "deferred",
};
output.push_str(" ");
output.push_str(fact.as_str());
output.push_str(": ");
output.push_str(value);
output.push('\n');
}
let label = match &self.trace.decisive {
DecisiveClause::Permit { label, .. } | DecisiveClause::Deny { label, .. } => label,
};
if let Some(label) = label {
output.push_str("decisive clause: ");
output.push_str(label.as_str());
output.push('\n');
}
output.push_str("Only consulted facts are shown; other checks were not reconstructed.");
output
}
}