1use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct CoverageReport {
15 pub hard_checks: HardCheckStatus,
18 pub transparency_included: bool,
21 pub time_anchored: bool,
24 pub dimensions_verified: Vec<String>,
26 pub dimension_count: usize,
28 pub independent_roots: usize,
31 pub multi_log_quorum: bool,
33 pub paths_found: usize,
35 pub downgrades: Vec<String>,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
41#[serde(rename_all = "snake_case")]
42pub enum HardCheckStatus {
43 Pass,
45 Fail,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
54pub struct ClassificationLabel(pub String);
55
56impl ClassificationLabel {
57 pub const REJECTED: &'static str = "rejected";
59
60 pub fn new(label: impl Into<String>) -> Self {
62 Self(label.into())
63 }
64}
65
66pub trait ClassificationPolicy {
70 fn classify(&self, report: &CoverageReport) -> ClassificationLabel;
72}
73
74#[derive(Debug, Clone, Default)]
84pub struct ReferenceClassificationPolicy;
85
86impl ClassificationPolicy for ReferenceClassificationPolicy {
87 fn classify(&self, report: &CoverageReport) -> ClassificationLabel {
88 if report.hard_checks == HardCheckStatus::Fail {
89 return ClassificationLabel::new(ClassificationLabel::REJECTED);
90 }
91 if !report.transparency_included {
92 return ClassificationLabel::new("unverified");
93 }
94 if !report.time_anchored {
95 return ClassificationLabel::new("basic");
96 }
97 let has_deprecated = report
101 .downgrades
102 .iter()
103 .any(|d| d.starts_with("deprecated_algorithm:"));
104 let dims: Vec<&str> = report
105 .dimensions_verified
106 .iter()
107 .map(|s| s.as_str())
108 .collect();
109 let has_person = dims.contains(&"person") && !has_deprecated;
110 if !has_person {
111 return ClassificationLabel::new("verified");
112 }
113 if report.independent_roots >= 2 {
114 ClassificationLabel::new("certified")
115 } else {
116 ClassificationLabel::new("attested")
117 }
118 }
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct AcceptancePolicy {
125 pub accepted_labels: Vec<String>,
127}
128
129impl AcceptancePolicy {
130 pub fn accept(labels: &[&str]) -> Self {
132 Self {
133 accepted_labels: labels.iter().map(|s| s.to_string()).collect(),
134 }
135 }
136
137 pub fn decide(&self, label: &ClassificationLabel) -> Acceptance {
139 if self.accepted_labels.iter().any(|l| l == &label.0) {
140 Acceptance::Accept
141 } else {
142 Acceptance::Reject
143 }
144 }
145}
146
147#[derive(Debug, Clone, Copy, PartialEq, Eq)]
149pub enum Acceptance {
150 Accept,
152 Reject,
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn report(t: bool, time: bool, dims: &[&str], roots: usize) -> CoverageReport {
161 CoverageReport {
162 hard_checks: HardCheckStatus::Pass,
163 transparency_included: t,
164 time_anchored: time,
165 dimensions_verified: dims.iter().map(|s| s.to_string()).collect(),
166 dimension_count: dims.len(),
167 independent_roots: roots,
168 multi_log_quorum: false,
169 paths_found: 1,
170 downgrades: vec![],
171 }
172 }
173
174 #[test]
175 fn reference_ladder() {
176 let p = ReferenceClassificationPolicy;
177 assert_eq!(
178 p.classify(&report(false, false, &["data"], 1)).0,
179 "unverified"
180 );
181 assert_eq!(p.classify(&report(true, false, &["data"], 1)).0, "basic");
182 assert_eq!(p.classify(&report(true, true, &["data"], 1)).0, "verified");
183 assert_eq!(
184 p.classify(&report(true, true, &["data", "person"], 1)).0,
185 "attested"
186 );
187 assert_eq!(
188 p.classify(&report(true, true, &["data", "person"], 2)).0,
189 "certified"
190 );
191
192 let mut failed = report(true, true, &["data"], 1);
193 failed.hard_checks = HardCheckStatus::Fail;
194 assert_eq!(p.classify(&failed).0, "rejected");
195 }
196
197 #[test]
198 fn acceptance_policy_decides_by_label() {
199 let policy = AcceptancePolicy::accept(&["verified", "attested", "certified"]);
200 assert_eq!(
201 policy.decide(&ClassificationLabel::new("attested")),
202 Acceptance::Accept
203 );
204 assert_eq!(
205 policy.decide(&ClassificationLabel::new("basic")),
206 Acceptance::Reject
207 );
208 assert_eq!(
209 policy.decide(&ClassificationLabel::new("rejected")),
210 Acceptance::Reject
211 );
212 }
213}