1use serde::{Deserialize, Serialize};
8
9use super::convergence_state::{ConvergencePointType, SubstrateType};
10use super::point_id::PointId;
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ComplianceBinding {
15 pub selector: PointSelector,
17 pub control: ComplianceControl,
19 pub phase: VerificationPhase,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "snake_case")]
26pub enum PointSelector {
27 ByType(ConvergencePointType),
29 BySubstrate(SubstrateType),
31 BySubstrateAndType(SubstrateType, ConvergencePointType),
33 ByEnvironment(String),
35 ByDataClassification(DataClassification),
37 ById(PointId),
39 All,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46pub enum VerificationPhase {
47 PlanTime,
49 AtBoundary,
51 PostConvergence,
53}
54
55#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct ComplianceControl {
58 pub framework: String,
60 pub control_id: String,
62 pub description: String,
64}
65
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
68#[serde(rename_all = "snake_case")]
69pub enum DataClassification {
70 Pii,
72 Phi,
74 Pci,
76 Public,
78 Internal,
80 Confidential,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, Default)]
87pub struct ComplianceClosure {
88 pub bindings: Vec<ComplianceBinding>,
90 pub resolved: Vec<ResolvedControl>,
92 pub plan_time_count: usize,
94 pub at_boundary_count: usize,
96 pub post_convergence_count: usize,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
102pub struct ResolvedControl {
103 pub control: ComplianceControl,
105 pub point_ids: Vec<PointId>,
107 pub phase: VerificationPhase,
109}
110
111impl PointSelector {
112 pub fn matches(
114 &self,
115 point_type: &ConvergencePointType,
116 substrate: &SubstrateType,
117 point_id: &PointId,
118 environment: Option<&str>,
119 data_class: Option<&DataClassification>,
120 ) -> bool {
121 match self {
122 Self::ByType(t) => t == point_type,
123 Self::BySubstrate(s) => s == substrate,
124 Self::BySubstrateAndType(s, t) => s == substrate && t == point_type,
125 Self::ByEnvironment(env) => environment == Some(env.as_str()),
126 Self::ByDataClassification(dc) => data_class == Some(dc),
127 Self::ById(id) => id == point_id,
128 Self::All => true,
129 }
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 fn sample_point_id() -> PointId {
138 PointId::compute(b"test", &[], b"state")
139 }
140
141 #[test]
142 fn test_selector_all_matches_everything() {
143 let sel = PointSelector::All;
144 assert!(sel.matches(
145 &ConvergencePointType::Transform,
146 &SubstrateType::Compute,
147 &sample_point_id(),
148 None,
149 None,
150 ));
151 }
152
153 #[test]
154 fn test_selector_by_substrate() {
155 let sel = PointSelector::BySubstrate(SubstrateType::Security);
156 assert!(sel.matches(
157 &ConvergencePointType::Gate,
158 &SubstrateType::Security,
159 &sample_point_id(),
160 None,
161 None,
162 ));
163 assert!(!sel.matches(
164 &ConvergencePointType::Gate,
165 &SubstrateType::Compute,
166 &sample_point_id(),
167 None,
168 None,
169 ));
170 }
171
172 #[test]
173 fn test_selector_by_type() {
174 let sel = PointSelector::ByType(ConvergencePointType::Gate);
175 assert!(sel.matches(
176 &ConvergencePointType::Gate,
177 &SubstrateType::Compute,
178 &sample_point_id(),
179 None,
180 None,
181 ));
182 assert!(!sel.matches(
183 &ConvergencePointType::Transform,
184 &SubstrateType::Compute,
185 &sample_point_id(),
186 None,
187 None,
188 ));
189 }
190
191 #[test]
192 fn test_selector_by_data_classification() {
193 let sel = PointSelector::ByDataClassification(DataClassification::Pii);
194 assert!(sel.matches(
195 &ConvergencePointType::Transform,
196 &SubstrateType::Storage,
197 &sample_point_id(),
198 None,
199 Some(&DataClassification::Pii),
200 ));
201 assert!(!sel.matches(
202 &ConvergencePointType::Transform,
203 &SubstrateType::Storage,
204 &sample_point_id(),
205 None,
206 Some(&DataClassification::Public),
207 ));
208 }
209
210 #[test]
211 fn test_verification_phase_serde() {
212 for phase in [
213 VerificationPhase::PlanTime,
214 VerificationPhase::AtBoundary,
215 VerificationPhase::PostConvergence,
216 ] {
217 let json = serde_json::to_string(&phase).unwrap();
218 let parsed: VerificationPhase = serde_json::from_str(&json).unwrap();
219 assert_eq!(phase, parsed);
220 }
221 }
222
223 #[test]
224 fn test_compliance_binding_serde() {
225 let binding = ComplianceBinding {
226 selector: PointSelector::BySubstrate(SubstrateType::Security),
227 control: ComplianceControl {
228 framework: "nist-800-53".into(),
229 control_id: "AC-6".into(),
230 description: "Least privilege".into(),
231 },
232 phase: VerificationPhase::PlanTime,
233 };
234 let json = serde_json::to_string(&binding).unwrap();
235 let _: ComplianceBinding = serde_json::from_str(&json).unwrap();
236 }
237}