Skip to main content

lsp_max/runtime/control_plane/
wasm4pm_graduation.rs

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}