perspt_sdk/
certificate.rs1use serde::{Deserialize, Serialize};
9
10use crate::residual::{CorrectionDirection, IndependenceRoute, ResidualEvent};
11
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14pub struct BudgetRef {
15 pub name: String,
16 pub limit: u64,
17 pub used: u64,
18}
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22pub struct ResidualCertificate {
23 pub certificate_id: String,
24 pub node_id: String,
25 pub generation: u32,
26 pub ledger_head: String,
28 pub final_energy: f64,
30 pub final_residuals: Vec<ResidualEvent>,
32 pub exhausted_budgets: Vec<BudgetRef>,
34 pub verifier_routes: Vec<IndependenceRoute>,
36 pub rejected_attempts: Vec<String>,
38 pub next_correction_directions: Vec<CorrectionDirection>,
40}
41
42impl ResidualCertificate {
43 pub fn from_residuals(
46 node_id: impl Into<String>,
47 generation: u32,
48 ledger_head: impl Into<String>,
49 final_energy: f64,
50 final_residuals: Vec<ResidualEvent>,
51 ) -> Self {
52 let mut verifier_routes: Vec<IndependenceRoute> = Vec::new();
53 let mut next_correction_directions: Vec<CorrectionDirection> = Vec::new();
54 for r in &final_residuals {
55 if !verifier_routes.contains(&r.sensor.route) {
56 verifier_routes.push(r.sensor.route);
57 }
58 next_correction_directions.extend(r.correction_directions.iter().cloned());
59 }
60 Self {
61 certificate_id: uuid::Uuid::new_v4().to_string(),
62 node_id: node_id.into(),
63 generation,
64 ledger_head: ledger_head.into(),
65 final_energy,
66 final_residuals,
67 exhausted_budgets: Vec::new(),
68 verifier_routes,
69 rejected_attempts: Vec::new(),
70 next_correction_directions,
71 }
72 }
73
74 pub fn with_exhausted_budget(mut self, budget: BudgetRef) -> Self {
75 self.exhausted_budgets.push(budget);
76 self
77 }
78
79 pub fn with_rejected_attempts(mut self, attempts: Vec<String>) -> Self {
80 self.rejected_attempts = attempts;
81 self
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use crate::residual::{ResidualClass, ResidualSeverity, SensorRef};
89
90 #[test]
91 fn certificate_derives_routes_and_directions() {
92 let residual = ResidualEvent::new(
93 "n1",
94 2,
95 ResidualClass::ImportGraph,
96 ResidualSeverity::Error,
97 1.0,
98 SensorRef::new("rust-analyzer", IndependenceRoute::Lsp),
99 )
100 .unwrap()
101 .with_correction(CorrectionDirection::new(
102 ResidualClass::ImportGraph,
103 "add `use crate::foo::Bar;`",
104 ));
105
106 let cert = ResidualCertificate::from_residuals("n1", 2, "head-abc", 1.0, vec![residual])
107 .with_exhausted_budget(BudgetRef {
108 name: "correction".into(),
109 limit: 4,
110 used: 4,
111 });
112
113 assert_eq!(cert.verifier_routes, vec![IndependenceRoute::Lsp]);
114 assert_eq!(cert.next_correction_directions.len(), 1);
115 assert_eq!(cert.exhausted_budgets.len(), 1);
116 assert_eq!(cert.node_id, "n1");
117 }
118}