1use crate::runtime::control_plane::admission::{
2 AdmittedData, CandidateData, GraphAdmissionLaw, QuarantinedData, RawData, RefusedData,
3 ReplayedData, SupersededData, ADMITTED, CANDIDATE, QUARANTINED, RAW, REFUSED, REPLAYED,
4 SUPERSEDED,
5};
6use crate::runtime::control_plane::invariants::VerificationReport;
7use crate::runtime::control_plane::receipts::{to_hex, CryptographicReceipt};
8use crate::runtime::Machine;
9use wasm4pm_compat::engine_bridge::{GraduateToWasm4pm, GraduationCandidate, GraduationReason};
10
11impl GraduateToWasm4pm for Machine<GraphAdmissionLaw, RAW, RawData> {
12 fn candidate(&self) -> GraduationCandidate {
13 let mut byte_accumulator = Vec::new();
14 for element in &self.data.elements {
15 if let Ok(serialized) = serde_json::to_vec(element) {
16 byte_accumulator.extend_from_slice(&serialized);
17 }
18 }
19 let graph_hash = crate::runtime::sha256(&byte_accumulator);
20 GraduationCandidate::new(
21 GraduationReason::NeedsDiscovery,
22 format!(
23 "RAW graph admission state with {} elements",
24 self.data.elements.len()
25 ),
26 format!("sha256:{}", graph_hash),
27 )
28 }
29}
30
31impl GraduateToWasm4pm for Machine<GraphAdmissionLaw, CANDIDATE, CandidateData> {
32 fn candidate(&self) -> GraduationCandidate {
33 GraduationCandidate::new(
34 GraduationReason::NeedsConformanceExecution,
35 format!(
36 "CANDIDATE graph state (elements: {}, quads: {})",
37 self.data.elements.len(),
38 self.data.quads.len()
39 ),
40 format!("sha256:{}", self.data.graph_hash),
41 )
42 }
43}
44
45impl GraduateToWasm4pm for Machine<GraphAdmissionLaw, ADMITTED, AdmittedData> {
46 fn candidate(&self) -> GraduationCandidate {
47 let payload_hash = self.data.receipt.compute_payload_hash();
48 GraduationCandidate::new(
49 GraduationReason::NeedsReceipts,
50 format!(
51 "ADMITTED graph state (quads: {}, graph: {:?}, receipt sequence: {})",
52 self.data.quad_count, self.data.graph_name, self.data.receipt.sequence
53 ),
54 format!("blake3:{}", to_hex(&payload_hash.0)),
55 )
56 }
57}
58
59impl GraduateToWasm4pm for Machine<GraphAdmissionLaw, REFUSED, RefusedData> {
60 fn candidate(&self) -> GraduationCandidate {
61 let mut hasher = blake3::Hasher::new();
62 if let Ok(serialized) = serde_json::to_vec(&self.data.report) {
63 hasher.update(&serialized);
64 } else {
65 hasher.update(b"unserializable report");
66 }
67 GraduationCandidate::new(
68 GraduationReason::NeedsConformanceExecution,
69 format!(
70 "REFUSED graph state (diagnostics: {}, execution_time: {}ms)",
71 self.data.report.diagnostics.len(),
72 self.data.report.execution_time_ms
73 ),
74 format!("blake3:{}", hasher.finalize()),
75 )
76 }
77}
78
79impl GraduateToWasm4pm for Machine<GraphAdmissionLaw, QUARANTINED, QuarantinedData> {
80 fn candidate(&self) -> GraduationCandidate {
81 let mut hasher = blake3::Hasher::new();
82 for dep in &self.data.missing_dependencies {
83 hasher.update(dep.as_bytes());
84 }
85 GraduationCandidate::new(
86 GraduationReason::NeedsReplay,
87 format!(
88 "QUARANTINED graph state (elements: {}, missing dependencies: {})",
89 self.data.elements.len(),
90 self.data.missing_dependencies.len()
91 ),
92 format!("blake3:{}", hasher.finalize()),
93 )
94 }
95}
96
97impl GraduateToWasm4pm for Machine<GraphAdmissionLaw, SUPERSEDED, SupersededData> {
98 fn candidate(&self) -> GraduationCandidate {
99 let mut hasher = blake3::Hasher::new();
100 hasher.update(format!("{:?}", self.data.graph_name).as_bytes());
101 hasher.update(format!("{:?}", self.data.superseded_by).as_bytes());
102 GraduationCandidate::new(
103 GraduationReason::RebuildingProcessMiningLocally,
104 format!(
105 "SUPERSEDED graph state (graph: {:?}, superseded by: {:?})",
106 self.data.graph_name, self.data.superseded_by
107 ),
108 format!("blake3:{}", hasher.finalize()),
109 )
110 }
111}
112
113impl GraduateToWasm4pm for Machine<GraphAdmissionLaw, REPLAYED, ReplayedData> {
114 fn candidate(&self) -> GraduationCandidate {
115 let payload_hash = self.data.receipt.compute_payload_hash();
116 GraduationCandidate::new(
117 GraduationReason::NeedsReplay,
118 format!(
119 "REPLAYED graph state (graph: {:?}, receipt sequence: {})",
120 self.data.graph_name, self.data.receipt.sequence
121 ),
122 format!("blake3:{}", to_hex(&payload_hash.0)),
123 )
124 }
125}
126
127impl GraduateToWasm4pm for CryptographicReceipt {
128 fn candidate(&self) -> GraduationCandidate {
129 let payload_hash = self.compute_payload_hash();
130 GraduationCandidate::new(
131 GraduationReason::NeedsReceipts,
132 format!("CryptographicReceipt (sequence: {})", self.sequence),
133 format!("blake3:{}", to_hex(&payload_hash.0)),
134 )
135 }
136}
137
138impl GraduateToWasm4pm for VerificationReport {
139 fn candidate(&self) -> GraduationCandidate {
140 let mut hasher = blake3::Hasher::new();
141 if let Ok(serialized) = serde_json::to_vec(self) {
142 hasher.update(&serialized);
143 } else {
144 hasher.update(b"unserializable verification report");
145 }
146 GraduationCandidate::new(
147 GraduationReason::NeedsConformanceExecution,
148 format!(
149 "VerificationReport (is_success: {}, diagnostics: {})",
150 self.is_success,
151 self.diagnostics.len()
152 ),
153 format!("blake3:{}", hasher.finalize()),
154 )
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::runtime::control_plane::receipts::Blake3Hash;
162 use lsp_max_lsif::lsif::{Element, PositionEncoding, Vertex, VertexType};
163
164 fn make_meta_element() -> Element {
165 Element::Vertex(Vertex::MetaData {
166 id: lsp_types_max::NumberOrString::Number(1),
167 type_: VertexType::Vertex,
168 version: "0.6.0".to_string(),
169 position_encoding: PositionEncoding::Utf16,
170 project_root: "file:///".to_string(),
171 tool_info: None,
172 })
173 }
174
175 #[test]
176 fn test_raw_graduation() {
177 let machine = Machine::new(
178 RAW,
179 RawData {
180 elements: vec![make_meta_element()],
181 },
182 );
183 let candidate = machine.candidate();
184 assert_eq!(candidate.reason, GraduationReason::NeedsDiscovery);
185 assert!(candidate.is_grounded());
186 assert!(candidate.subject.contains("RAW graph"));
187 }
188
189 #[test]
190 fn test_candidate_graduation() {
191 let machine = Machine::new(
192 CANDIDATE,
193 CandidateData {
194 elements: vec![],
195 quads: vec![],
196 graph_hash: "abcd".to_string(),
197 },
198 );
199 let candidate = machine.candidate();
200 assert_eq!(
201 candidate.reason,
202 GraduationReason::NeedsConformanceExecution
203 );
204 assert!(candidate.is_grounded());
205 assert_eq!(candidate.evidence_ref, "sha256:abcd");
206 }
207
208 #[test]
209 fn test_admitted_graduation() {
210 let receipt = CryptographicReceipt {
211 prev_hash: Blake3Hash([0u8; 32]),
212 discipline_id: uuid::Uuid::nil(),
213 law_id: uuid::Uuid::nil(),
214 consequence_hash: Blake3Hash([1u8; 32]),
215 sequence: 42,
216 signature: [0u8; 64],
217 };
218 let machine = Machine::new(
219 ADMITTED,
220 AdmittedData {
221 graph_name: oxigraph::model::GraphName::DefaultGraph,
222 quad_count: 10,
223 receipt,
224 },
225 );
226 let candidate = machine.candidate();
227 assert_eq!(candidate.reason, GraduationReason::NeedsReceipts);
228 assert!(candidate.is_grounded());
229 assert!(candidate.subject.contains("ADMITTED graph"));
230 }
231
232 #[test]
233 fn test_refused_graduation() {
234 let report = VerificationReport {
235 is_success: false,
236 diagnostics: vec![],
237 execution_time_ms: 120,
238 };
239 let machine = Machine::new(REFUSED, RefusedData { report });
240 let candidate = machine.candidate();
241 assert_eq!(
242 candidate.reason,
243 GraduationReason::NeedsConformanceExecution
244 );
245 assert!(candidate.is_grounded());
246 assert!(candidate.subject.contains("REFUSED graph"));
247 }
248
249 #[test]
250 fn test_quarantined_graduation() {
251 let machine = Machine::new(
252 QUARANTINED,
253 QuarantinedData {
254 elements: vec![],
255 quads: vec![],
256 missing_dependencies: vec!["urn:dep:1".to_string()],
257 },
258 );
259 let candidate = machine.candidate();
260 assert_eq!(candidate.reason, GraduationReason::NeedsReplay);
261 assert!(candidate.is_grounded());
262 assert!(candidate.subject.contains("QUARANTINED graph"));
263 }
264
265 #[test]
266 fn test_superseded_graduation() {
267 let machine = Machine::new(
268 SUPERSEDED,
269 SupersededData {
270 graph_name: oxigraph::model::GraphName::DefaultGraph,
271 superseded_by: oxigraph::model::GraphName::DefaultGraph,
272 },
273 );
274 let candidate = machine.candidate();
275 assert_eq!(
276 candidate.reason,
277 GraduationReason::RebuildingProcessMiningLocally
278 );
279 assert!(candidate.is_grounded());
280 assert!(candidate.subject.contains("SUPERSEDED graph"));
281 }
282
283 #[test]
284 fn test_replayed_graduation() {
285 let receipt = CryptographicReceipt {
286 prev_hash: Blake3Hash([0u8; 32]),
287 discipline_id: uuid::Uuid::nil(),
288 law_id: uuid::Uuid::nil(),
289 consequence_hash: Blake3Hash([2u8; 32]),
290 sequence: 12,
291 signature: [0u8; 64],
292 };
293 let machine = Machine::new(
294 REPLAYED,
295 ReplayedData {
296 graph_name: oxigraph::model::GraphName::DefaultGraph,
297 receipt,
298 },
299 );
300 let candidate = machine.candidate();
301 assert_eq!(candidate.reason, GraduationReason::NeedsReplay);
302 assert!(candidate.is_grounded());
303 assert!(candidate.subject.contains("REPLAYED graph"));
304 }
305
306 #[test]
307 fn test_receipt_graduation() {
308 let receipt = CryptographicReceipt {
309 prev_hash: Blake3Hash([0u8; 32]),
310 discipline_id: uuid::Uuid::nil(),
311 law_id: uuid::Uuid::nil(),
312 consequence_hash: Blake3Hash([3u8; 32]),
313 sequence: 99,
314 signature: [0u8; 64],
315 };
316 let candidate = receipt.candidate();
317 assert_eq!(candidate.reason, GraduationReason::NeedsReceipts);
318 assert!(candidate.is_grounded());
319 assert!(candidate.subject.contains("CryptographicReceipt"));
320 }
321
322 #[test]
323 fn test_verification_report_graduation() {
324 let report = VerificationReport {
325 is_success: true,
326 diagnostics: vec![],
327 execution_time_ms: 10,
328 };
329 let candidate = report.candidate();
330 assert_eq!(
331 candidate.reason,
332 GraduationReason::NeedsConformanceExecution
333 );
334 assert!(candidate.is_grounded());
335 assert!(candidate.subject.contains("VerificationReport"));
336 }
337}