phytrace_sdk/models/domains/
compliance.rs

1//! Compliance Domain - Certifications, safety standards, cybersecurity.
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use validator::Validate;
6
7/// Compliance domain.
8#[derive(Debug, Clone, Default, Serialize, Deserialize)]
9pub struct ComplianceDomain {
10    /// Certifications
11    #[serde(skip_serializing_if = "Option::is_none")]
12    pub certifications: Option<Vec<Certification>>,
13
14    /// Functional safety
15    #[serde(skip_serializing_if = "Option::is_none")]
16    pub functional_safety: Option<FunctionalSafety>,
17
18    /// Cybersecurity
19    #[serde(skip_serializing_if = "Option::is_none")]
20    pub cybersecurity: Option<Cybersecurity>,
21
22    /// Regulatory status
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub regulatory: Option<RegulatoryStatus>,
25}
26
27/// Certification record.
28#[derive(Debug, Clone, Default, Serialize, Deserialize)]
29pub struct Certification {
30    /// Certification ID
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub cert_id: Option<String>,
33
34    /// Standard name (e.g., "ISO 13482", "CE", "UL")
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub standard: Option<String>,
37
38    /// Certification body
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub certifying_body: Option<String>,
41
42    /// Issue date
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub issue_date: Option<DateTime<Utc>>,
45
46    /// Expiry date
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub expiry_date: Option<DateTime<Utc>>,
49
50    /// Whether currently valid
51    #[serde(skip_serializing_if = "Option::is_none")]
52    pub is_valid: Option<bool>,
53
54    /// Certification scope
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub scope: Option<String>,
57
58    /// Document reference
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub document_ref: Option<String>,
61}
62
63/// Functional safety status per ISO 61508/13849.
64#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
65pub struct FunctionalSafety {
66    /// Safety Integrity Level (SIL 1-4)
67    #[serde(skip_serializing_if = "Option::is_none")]
68    #[validate(range(min = 1, max = 4))]
69    pub sil_level: Option<u8>,
70
71    /// Performance Level (a-e)
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub performance_level: Option<String>,
74
75    /// Safety function status
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub safety_functions: Option<Vec<SafetyFunction>>,
78
79    /// Last safety test timestamp
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub last_test: Option<DateTime<Utc>>,
82
83    /// Diagnostic coverage (0-100%)
84    #[serde(skip_serializing_if = "Option::is_none")]
85    #[validate(range(min = 0.0, max = 100.0))]
86    pub diagnostic_coverage: Option<f64>,
87
88    /// Safe failure fraction (0-100%)
89    #[serde(skip_serializing_if = "Option::is_none")]
90    #[validate(range(min = 0.0, max = 100.0))]
91    pub safe_failure_fraction: Option<f64>,
92
93    /// Mean Time To Failure (hours)
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub mttf_hours: Option<f64>,
96}
97
98/// Individual safety function status.
99#[derive(Debug, Clone, Default, Serialize, Deserialize)]
100pub struct SafetyFunction {
101    /// Function ID
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub function_id: Option<String>,
104
105    /// Function name
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub name: Option<String>,
108
109    /// Whether active
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub is_active: Option<bool>,
112
113    /// Status
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub status: Option<String>,
116
117    /// Last check timestamp
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub last_check: Option<DateTime<Utc>>,
120}
121
122/// Cybersecurity status.
123#[derive(Debug, Clone, Default, Serialize, Deserialize, Validate)]
124pub struct Cybersecurity {
125    /// Overall security posture (0-100)
126    #[serde(skip_serializing_if = "Option::is_none")]
127    #[validate(range(min = 0.0, max = 100.0))]
128    pub security_score: Option<f64>,
129
130    /// IEC 62443 Security Level (1-4)
131    #[serde(skip_serializing_if = "Option::is_none")]
132    #[validate(range(min = 1, max = 4))]
133    pub security_level: Option<u8>,
134
135    /// Encryption status
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub encryption: Option<EncryptionStatus>,
138
139    /// Authentication status
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub authentication: Option<AuthenticationStatus>,
142
143    /// Network security
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub network_security: Option<NetworkSecurity>,
146
147    /// Active vulnerabilities
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub vulnerabilities: Option<Vec<Vulnerability>>,
150
151    /// Last security audit
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub last_audit: Option<DateTime<Utc>>,
154
155    /// Last patch update
156    #[serde(skip_serializing_if = "Option::is_none")]
157    pub last_patch: Option<DateTime<Utc>>,
158}
159
160/// Encryption status.
161#[derive(Debug, Clone, Default, Serialize, Deserialize)]
162pub struct EncryptionStatus {
163    /// Data at rest encrypted
164    #[serde(skip_serializing_if = "Option::is_none")]
165    pub data_at_rest: Option<bool>,
166
167    /// Data in transit encrypted
168    #[serde(skip_serializing_if = "Option::is_none")]
169    pub data_in_transit: Option<bool>,
170
171    /// Encryption algorithm
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub algorithm: Option<String>,
174
175    /// Key strength (bits)
176    #[serde(skip_serializing_if = "Option::is_none")]
177    pub key_strength_bits: Option<u32>,
178}
179
180/// Authentication status.
181#[derive(Debug, Clone, Default, Serialize, Deserialize)]
182pub struct AuthenticationStatus {
183    /// Authentication method
184    #[serde(skip_serializing_if = "Option::is_none")]
185    pub method: Option<String>,
186
187    /// Multi-factor auth enabled
188    #[serde(skip_serializing_if = "Option::is_none")]
189    pub mfa_enabled: Option<bool>,
190
191    /// Certificate-based auth
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub cert_auth: Option<bool>,
194
195    /// Token expiry (seconds)
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub token_expiry_sec: Option<u64>,
198}
199
200/// Network security status.
201#[derive(Debug, Clone, Default, Serialize, Deserialize)]
202pub struct NetworkSecurity {
203    /// Firewall enabled
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub firewall_enabled: Option<bool>,
206
207    /// VPN connected
208    #[serde(skip_serializing_if = "Option::is_none")]
209    pub vpn_connected: Option<bool>,
210
211    /// Network segmentation
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub segmented: Option<bool>,
214
215    /// Open ports
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub open_ports: Option<Vec<u16>>,
218
219    /// Intrusion detection enabled
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub ids_enabled: Option<bool>,
222}
223
224/// Known vulnerability.
225#[derive(Debug, Clone, Default, Serialize, Deserialize)]
226pub struct Vulnerability {
227    /// CVE ID
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub cve_id: Option<String>,
230
231    /// Severity (critical, high, medium, low)
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub severity: Option<String>,
234
235    /// CVSS score (0-10)
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub cvss_score: Option<f64>,
238
239    /// Affected component
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub component: Option<String>,
242
243    /// Whether patched
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub is_patched: Option<bool>,
246
247    /// Discovered timestamp
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub discovered: Option<DateTime<Utc>>,
250}
251
252/// Regulatory compliance status.
253#[derive(Debug, Clone, Default, Serialize, Deserialize)]
254pub struct RegulatoryStatus {
255    /// Operating region
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub region: Option<String>,
258
259    /// Required certifications
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub required_certs: Option<Vec<String>>,
262
263    /// Compliance status (compliant, non-compliant, pending)
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub status: Option<String>,
266
267    /// Pending requirements
268    #[serde(skip_serializing_if = "Option::is_none")]
269    pub pending_requirements: Option<Vec<String>>,
270
271    /// Last compliance check
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub last_check: Option<DateTime<Utc>>,
274}
275
276impl ComplianceDomain {
277    /// Add a certification.
278    pub fn with_certification(mut self, cert: Certification) -> Self {
279        let certs = self.certifications.get_or_insert_with(Vec::new);
280        certs.push(cert);
281        self
282    }
283
284    /// Set functional safety.
285    pub fn with_functional_safety(mut self, safety: FunctionalSafety) -> Self {
286        self.functional_safety = Some(safety);
287        self
288    }
289
290    /// Set cybersecurity.
291    pub fn with_cybersecurity(mut self, cyber: Cybersecurity) -> Self {
292        self.cybersecurity = Some(cyber);
293        self
294    }
295}
296
297impl Certification {
298    /// Create a valid certification.
299    pub fn new(standard: impl Into<String>) -> Self {
300        Self {
301            standard: Some(standard.into()),
302            is_valid: Some(true),
303            ..Default::default()
304        }
305    }
306
307    /// Check if certification is expired.
308    pub fn is_expired(&self) -> bool {
309        self.expiry_date
310            .map(|exp| exp < Utc::now())
311            .unwrap_or(false)
312    }
313}
314
315impl FunctionalSafety {
316    /// Create with SIL level.
317    pub fn with_sil(sil_level: u8) -> Self {
318        Self {
319            sil_level: Some(sil_level.clamp(1, 4)),
320            ..Default::default()
321        }
322    }
323}
324
325#[cfg(test)]
326mod tests {
327    use super::*;
328
329    #[test]
330    fn test_certification() {
331        let cert = Certification::new("ISO 13482");
332        assert_eq!(cert.standard, Some("ISO 13482".to_string()));
333        assert_eq!(cert.is_valid, Some(true));
334    }
335
336    #[test]
337    fn test_functional_safety() {
338        let safety = FunctionalSafety::with_sil(3);
339        assert_eq!(safety.sil_level, Some(3));
340    }
341
342    #[test]
343    fn test_compliance_domain() {
344        let compliance = ComplianceDomain::default()
345            .with_certification(Certification::new("CE"))
346            .with_functional_safety(FunctionalSafety::with_sil(2));
347
348        assert_eq!(compliance.certifications.unwrap().len(), 1);
349        assert!(compliance.functional_safety.is_some());
350    }
351}