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 `Diagnostic`.
102    pub fn into_lsp(self) -> Diagnostic {
103        self.lsp
104    }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct Precondition {
109    pub condition: String,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct ValidationPlan {
114    pub gates: Vec<GateId>,
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct RollbackPlan {
119    pub strategy: String,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ReceiptPlan {
124    pub expected_receipts: Vec<String>,
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct MaxCodeAction {
129    pub action: CodeAction,
130    pub preconditions: Vec<Precondition>,
131    pub validation_plan: ValidationPlan,
132    pub rollback_plan: RollbackPlan,
133    pub receipt_plan: ReceiptPlan,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct SnapshotId(pub String);
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn max_diagnostic_default_is_valid() {
145        let d = MaxDiagnostic::default();
146        assert!(d.diagnostic_id.is_empty());
147        assert!(d.violated_axes.is_empty());
148        assert!(d.repair_actions.is_empty());
149    }
150
151    #[test]
152    fn max_diagnostic_into_lsp_returns_lsp_field() {
153        let d = MaxDiagnostic {
154            diagnostic_id: "diag-1".to_string(),
155            law_id: "LSP-001".to_string(),
156            ..MaxDiagnostic::default()
157        };
158        let lsp = d.into_lsp();
159        // into_lsp projects out the lsp field unchanged
160        assert_eq!(lsp.message, String::new());
161    }
162}