1use crate::{GateId, LawAxis, ReceiptObligation};
2use lsp_types_max::{CodeAction, Diagnostic};
3use serde::{Deserialize, Serialize};
4
5#[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#[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 #[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 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}