lsp_max/runtime/
ledger.rs1use crate::runtime::mesh::AutonomicMesh;
2use crate::runtime::mesh_types::LspPhase;
3use crate::runtime::sha256::sha256;
4
5impl AutonomicMesh {
6 pub fn verify_instance_ledger(&self, instance_id: &str) -> Result<(), String> {
7 let instance = self
8 .instances
9 .get(instance_id)
10 .ok_or_else(|| format!("Instance {} not found", instance_id))?;
11
12 let history = &instance.receipts;
13 if history.is_empty() {
14 return Err("Ledger is empty".to_string());
15 }
16
17 if instance_id == "LSP_1" {
18 let r0 = &history[0];
19 if r0.receipt_id != "rcpt-uninitialized" {
20 return Err(format!("Invalid initial receipt ID: {}", r0.receipt_id));
21 }
22 let mut expected_hash = sha256(r0.receipt_id.as_bytes());
23 if r0.hash != expected_hash {
24 return Err(format!(
25 "Hash mismatch at index 0: expected {}, got {}",
26 expected_hash, r0.hash
27 ));
28 }
29
30 if history.len() > 1 {
31 let r1 = &history[1];
32 if !r1
33 .receipt_id
34 .starts_with("rcpt-uninitialized-to-initializing:")
35 {
36 return Err(format!("Invalid receipt ID at index 1: {}", r1.receipt_id));
37 }
38 let prefix_len = "rcpt-uninitialized-to-initializing:".len();
39 let json_str = &r1.receipt_id[prefix_len..];
40 if serde_json::from_str::<serde_json::Value>(json_str).is_err() {
41 return Err("Failed to parse client capabilities in receipt 1".to_string());
42 }
43
44 expected_hash = sha256(format!("{}:{}", expected_hash, r1.receipt_id).as_bytes());
45 if r1.hash != expected_hash {
46 return Err(format!(
47 "Hash mismatch at index 1: expected {}, got {}",
48 expected_hash, r1.hash
49 ));
50 }
51 }
52
53 if history.len() > 2 {
54 let r2 = &history[2];
55 if !r2
56 .receipt_id
57 .starts_with("rcpt-initializing-to-initialized:")
58 {
59 return Err(format!("Invalid receipt ID at index 2: {}", r2.receipt_id));
60 }
61 let prefix_len = "rcpt-initializing-to-initialized:".len();
62 let json_str = &r2.receipt_id[prefix_len..];
63 if serde_json::from_str::<serde_json::Value>(json_str).is_err() {
64 return Err("Failed to parse server capabilities in receipt 2".to_string());
65 }
66
67 expected_hash = sha256(format!("{}:{}", expected_hash, r2.receipt_id).as_bytes());
68 if r2.hash != expected_hash {
69 return Err(format!(
70 "Hash mismatch at index 2: expected {}, got {}",
71 expected_hash, r2.hash
72 ));
73 }
74 }
75
76 if history.len() > 3 {
77 let r3 = &history[3];
78 if r3.receipt_id != "rcpt-initialized-to-shutdown" {
79 return Err(format!("Invalid receipt ID at index 3: {}", r3.receipt_id));
80 }
81 expected_hash = sha256(format!("{}:{}", expected_hash, r3.receipt_id).as_bytes());
82 if r3.hash != expected_hash {
83 return Err(format!(
84 "Hash mismatch at index 3: expected {}, got {}",
85 expected_hash, r3.hash
86 ));
87 }
88 }
89
90 if history.len() > 4 {
91 let r4 = &history[4];
92 if r4.receipt_id != "rcpt-shutdown-to-exited" {
93 return Err(format!("Invalid receipt ID at index 4: {}", r4.receipt_id));
94 }
95 expected_hash = sha256(format!("{}:{}", expected_hash, r4.receipt_id).as_bytes());
96 if r4.hash != expected_hash {
97 return Err(format!(
98 "Hash mismatch at index 4: expected {}, got {}",
99 expected_hash, r4.hash
100 ));
101 }
102 }
103
104 if history.len() > 5 {
105 return Err("Ledger contains unexpected items beyond Exited state".to_string());
106 }
107
108 let expected_phase = match history.len() {
109 1 => LspPhase::Uninitialized,
110 2 => LspPhase::Initializing,
111 3 => LspPhase::Initialized,
112 4 => LspPhase::ShutDown,
113 5 => LspPhase::Exited,
114 _ => unreachable!(),
115 };
116
117 if instance.phase != expected_phase {
118 return Err(format!(
119 "Phase mismatch: instance.phase is '{}' but ledger shows '{}'",
120 instance.phase, expected_phase
121 ));
122 }
123 } else {
124 for (idx, r) in history.iter().enumerate() {
125 if r.receipt_id.is_empty() {
126 return Err(format!("Empty receipt ID at index {}", idx));
127 }
128 if r.hash.is_empty() {
129 return Err(format!("Empty receipt hash at index {}", idx));
130 }
131 }
132 }
133
134 Ok(())
135 }
136
137 pub fn get_ledger_diagnostic_report(&self, instance_id: &str) -> String {
138 let mut report = format!("Ledger Diagnostic Report for Instance: {}\n", instance_id);
139 match self.verify_instance_ledger(instance_id) {
140 Ok(()) => {
141 report.push_str("Status: ADMITTED (Cryptographic integrity confirmed)\n");
142 }
143 Err(e) => {
144 report.push_str(&format!(
145 "Status: REFUSED (Ledger verification refused: {})\n",
146 e
147 ));
148 }
149 }
150
151 if let Some(instance) = self.instances.get(instance_id) {
152 report.push_str(&format!("Active Phase: {}\n", instance.phase));
153 report.push_str(&format!("Policy State: {:?}\n", instance.policy_state));
154 report.push_str(&format!("Receipts count: {}\n", instance.receipts.len()));
155 for (idx, r) in instance.receipts.iter().enumerate() {
156 report.push_str(&format!(
157 " [{}] ID: {} | Hash: {}\n",
158 idx, r.receipt_id, r.hash
159 ));
160 }
161 } else {
162 report.push_str("Instance not found in mesh registry.\n");
163 }
164 report
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use crate::runtime::mesh_types::{LspInstance, Receipt};
172
173 fn make_mesh_with_instance(instance: LspInstance) -> AutonomicMesh {
174 let mut mesh = AutonomicMesh::new();
175 mesh.add_instance(instance);
176 mesh
177 }
178
179 #[test]
182 fn ledger_unknown_instance_is_err() {
183 let mesh = AutonomicMesh::new();
184 let result = mesh.verify_instance_ledger("NONEXISTENT");
185 assert!(result.is_err());
186 assert!(result.unwrap_err().contains("not found"));
187 }
188
189 #[test]
192 fn ledger_empty_receipts_is_err() {
193 let instance = LspInstance::new("ANY_ID");
194 let mesh = make_mesh_with_instance(instance);
196 let result = mesh.verify_instance_ledger("ANY_ID");
197 assert!(result.is_err());
198 assert!(result.unwrap_err().contains("empty"));
199 }
200
201 #[test]
204 fn ledger_generic_instance_valid_receipt() {
205 let mut instance = LspInstance::new("LSP_2");
208 instance.receipts.push(Receipt {
209 receipt_id: "rcpt-some-event".to_string(),
210 hash: "deadbeef".to_string(),
211 prev_receipt_hash: None,
212 });
213 let mesh = make_mesh_with_instance(instance);
214 let result = mesh.verify_instance_ledger("LSP_2");
215 assert!(
216 result.is_ok(),
217 "non-empty receipt on generic instance must pass"
218 );
219 }
220
221 #[test]
222 fn ledger_generic_instance_empty_receipt_id_is_err() {
223 let mut instance = LspInstance::new("LSP_3");
224 instance.receipts.push(Receipt {
225 receipt_id: String::new(), hash: "deadbeef".to_string(),
227 prev_receipt_hash: None,
228 });
229 let mesh = make_mesh_with_instance(instance);
230 let result = mesh.verify_instance_ledger("LSP_3");
231 assert!(result.is_err(), "empty receipt_id must return Err");
232 assert!(result.unwrap_err().contains("Empty receipt ID"));
233 }
234
235 #[test]
236 fn ledger_generic_instance_empty_hash_is_err() {
237 let mut instance = LspInstance::new("LSP_4");
238 instance.receipts.push(Receipt {
239 receipt_id: "rcpt-something".to_string(),
240 hash: String::new(), prev_receipt_hash: None,
242 });
243 let mesh = make_mesh_with_instance(instance);
244 let result = mesh.verify_instance_ledger("LSP_4");
245 assert!(result.is_err(), "empty receipt hash must return Err");
246 assert!(result.unwrap_err().contains("Empty receipt hash"));
247 }
248
249 #[test]
252 fn ledger_lsp1_valid_genesis() {
253 let mut instance = LspInstance::new("LSP_1");
254 instance.phase = LspPhase::Uninitialized;
255 let h0 = sha256(b"rcpt-uninitialized");
256 instance.receipts.push(Receipt {
257 receipt_id: "rcpt-uninitialized".to_string(),
258 hash: h0,
259 prev_receipt_hash: None,
260 });
261 let mesh = make_mesh_with_instance(instance);
262 let result = mesh.verify_instance_ledger("LSP_1");
263 assert!(result.is_ok(), "valid genesis receipt for LSP_1 must pass");
264 }
265
266 #[test]
267 fn ledger_lsp1_wrong_genesis_id_is_err() {
268 let mut instance = LspInstance::new("LSP_1");
269 instance.phase = LspPhase::Uninitialized;
270 instance.receipts.push(Receipt {
271 receipt_id: "rcpt-wrong".to_string(),
272 hash: sha256(b"rcpt-wrong"),
273 prev_receipt_hash: None,
274 });
275 let mesh = make_mesh_with_instance(instance);
276 let result = mesh.verify_instance_ledger("LSP_1");
277 assert!(result.is_err(), "wrong genesis receipt_id must return Err");
278 }
279
280 #[test]
281 fn ledger_lsp1_tampered_genesis_hash_is_err() {
282 let mut instance = LspInstance::new("LSP_1");
283 instance.phase = LspPhase::Uninitialized;
284 instance.receipts.push(Receipt {
285 receipt_id: "rcpt-uninitialized".to_string(),
286 hash: "0000000000000000000000000000000000000000000000000000000000000000".to_string(),
287 prev_receipt_hash: None,
288 });
289 let mesh = make_mesh_with_instance(instance);
290 let result = mesh.verify_instance_ledger("LSP_1");
291 assert!(result.is_err(), "tampered genesis hash must return Err");
292 }
293}