Skip to main content

lsp_max_protocol/
diagnostics.rs

1use crate::{GateId, LawAxis, ReceiptObligation};
2use lsp_types_max::{CodeAction, Diagnostic};
3use serde::{Deserialize, Serialize};
4
5// ---------------------------------------------------------------------------
6// Repairability / Terminality
7// ---------------------------------------------------------------------------
8
9#[derive(Debug, Clone, Default, Serialize, Deserialize)]
10pub enum Repairability {
11    Repairable,
12    NotRepairable,
13    #[default]
14    Unknown,
15}
16
17#[derive(Debug, Clone, Default, Serialize, Deserialize)]
18pub enum Terminality {
19    Terminal,
20    #[default]
21    NonTerminal,
22}
23
24impl std::fmt::Display for Terminality {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        write!(f, "{:?}", self)
27    }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct TransitionAttempt {
32    pub from_state: String,
33    pub to_state: String,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
37pub struct DocRoute {
38    pub path: String,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct RepairAction {
43    pub action_id: String,
44    pub description: String,
45}
46
47// ---------------------------------------------------------------------------
48// MaxDiagnostic — extended with doctrine fields
49// ---------------------------------------------------------------------------
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct MaxDiagnostic {
53    pub lsp: Diagnostic,
54    pub diagnostic_id: String,
55    pub law_id: String,
56    pub attempted_transition: Option<TransitionAttempt>,
57    pub violated_axes: Vec<String>,
58    pub doc_routes: Vec<DocRoute>,
59    pub repair_actions: Vec<RepairAction>,
60    pub verification_gates: Vec<GateId>,
61    pub receipt_obligation: Option<ReceiptObligation>,
62
63    // Doctrine extensions (serde(default) preserves backward compatibility)
64    #[serde(default)]
65    pub law_axis: LawAxis,
66    #[serde(default)]
67    pub violated_invariant: String,
68    #[serde(default)]
69    pub observed_state: serde_json::Value,
70    #[serde(default)]
71    pub expected_state: serde_json::Value,
72    #[serde(default)]
73    pub repairability: Repairability,
74    #[serde(default)]
75    pub terminality: Terminality,
76}
77
78impl Default for MaxDiagnostic {
79    fn default() -> Self {
80        Self {
81            lsp: Diagnostic::default(),
82            diagnostic_id: String::new(),
83            law_id: String::new(),
84            attempted_transition: None,
85            violated_axes: Vec::new(),
86            doc_routes: Vec::new(),
87            repair_actions: Vec::new(),
88            verification_gates: Vec::new(),
89            receipt_obligation: None,
90            law_axis: LawAxis::default(),
91            violated_invariant: String::new(),
92            observed_state: serde_json::Value::Null,
93            expected_state: serde_json::Value::Null,
94            repairability: Repairability::default(),
95            terminality: Terminality::default(),
96        }
97    }
98}
99
100impl MaxDiagnostic {
101    /// Projects the `MaxDiagnostic` down into a standard `lsp_types_max::Diagnostic`.
102    pub fn into_lsp(self) -> Diagnostic {
103        let mut d = self.lsp.clone();
104        if d.data.is_none() {
105            if let Ok(data) = serde_json::to_value(self) {
106                d.data = Some(data);
107            }
108        }
109        d
110    }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct Precondition {
115    pub condition: String,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct ValidationPlan {
120    pub gates: Vec<GateId>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct RollbackPlan {
125    pub strategy: String,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ReceiptPlan {
130    pub expected_receipts: Vec<String>,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct MaxCodeAction {
135    pub action: CodeAction,
136    pub preconditions: Vec<Precondition>,
137    pub validation_plan: ValidationPlan,
138    pub rollback_plan: RollbackPlan,
139    pub receipt_plan: ReceiptPlan,
140}
141
142#[derive(Debug, Clone, Serialize, Deserialize)]
143pub struct SnapshotId(pub String);
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn max_diagnostic_default_is_valid() {
151        let d = MaxDiagnostic::default();
152        assert!(d.diagnostic_id.is_empty());
153        assert!(d.violated_axes.is_empty());
154        assert!(d.repair_actions.is_empty());
155    }
156
157    #[test]
158    fn max_diagnostic_into_lsp_preserves_data() {
159        let d = MaxDiagnostic {
160            diagnostic_id: "diag-1".to_string(),
161            law_id: "LSP-001".to_string(),
162            ..MaxDiagnostic::default()
163        };
164        let lsp = d.into_lsp();
165        assert!(lsp.data.is_some());
166    }
167}