Skip to main content

lsp_max/runtime/
mesh.rs

1use crate::runtime::mesh_hooks::{IntakeClearHook, IntakeDiagnosticHook, OcelProcessHook};
2use crate::runtime::mesh_types::{
3    AutonomicMeshState, ConformanceDeltaEntry, Hook, HookEvent, InstanceId, LspInstance, LspPhase,
4    MaxDiagnostic, MeshAction, PolicyState,
5};
6use crate::runtime::sha256::sha256;
7use std::collections::{HashMap, VecDeque};
8use std::io;
9use std::path::Path;
10
11const MAX_EVENT_LOG: usize = 1000;
12const MAX_DISPATCH_DEPTH: usize = 16;
13const MAX_CONFORMANCE_DELTA_LOG: usize = 4096;
14
15pub struct AutonomicMesh {
16    pub instances: HashMap<String, LspInstance>,
17    pub hooks: Vec<Box<dyn Hook>>,
18    pub event_log: Vec<HookEvent>,
19    pub executed_bounded_actions: Vec<String>,
20    pub extra: HashMap<String, serde_json::Value>,
21    pub action_seq: u64,
22    pub conformance_delta_log: VecDeque<ConformanceDeltaEntry>,
23    dispatch_depth: usize,
24}
25
26impl Default for AutonomicMesh {
27    fn default() -> Self {
28        Self::new()
29    }
30}
31
32pub type MaxMesh = AutonomicMesh;
33
34pub fn build_conformance_vector(
35    diagnostics: &[MaxDiagnostic],
36) -> lsp_max_protocol::ConformanceVector {
37    let mut axis_map: HashMap<lsp_max_protocol::LawAxis, bool> = HashMap::new();
38    for diag in diagnostics {
39        let is_error = matches!(
40            diag.lsp.severity,
41            Some(lsp_types_max::DiagnosticSeverity::ERROR)
42        );
43        let entry = axis_map.entry(diag.law_axis.clone()).or_insert(false);
44        if is_error {
45            *entry = true;
46        }
47    }
48
49    let mut admitted = vec![];
50    let mut refused = vec![];
51    for (axis, has_error) in &axis_map {
52        if *has_error {
53            refused.push(axis.clone());
54        } else {
55            admitted.push(axis.clone());
56        }
57    }
58
59    let total = admitted.len() + refused.len();
60    let derived_score = if total == 0 {
61        None
62    } else {
63        Some(100.0 * admitted.len() as f64 / total as f64)
64    };
65
66    let witnessed: std::collections::HashSet<lsp_max_protocol::LawAxis> =
67        axis_map.keys().cloned().collect();
68    let unknown: Vec<lsp_max_protocol::LawAxis> = lsp_max_protocol::LawAxis::all_named()
69        .iter()
70        .filter(|ax| !witnessed.contains(ax))
71        .cloned()
72        .collect();
73
74    let mut cv = lsp_max_protocol::ConformanceVector {
75        admitted,
76        refused,
77        unknown,
78        score: derived_score,
79        strict_mode: true,
80        process_quality: None,
81        ..Default::default()
82    };
83    cv.sync_bits_from_vecs();
84    cv
85}
86
87impl AutonomicMesh {
88    pub fn new() -> Self {
89        Self {
90            instances: HashMap::new(),
91            hooks: Vec::new(),
92            event_log: Vec::new(),
93            executed_bounded_actions: Vec::new(),
94            extra: HashMap::new(),
95            action_seq: 0,
96            conformance_delta_log: VecDeque::new(),
97            dispatch_depth: 0,
98        }
99    }
100
101    pub fn to_state(&self) -> AutonomicMeshState {
102        AutonomicMeshState {
103            instances: self.instances.clone(),
104            event_log: self.event_log.clone(),
105            executed_bounded_actions: self.executed_bounded_actions.clone(),
106            extra: self.extra.clone(),
107        }
108    }
109
110    pub fn load_state(&mut self, state: AutonomicMeshState) {
111        self.instances = state.instances;
112        self.event_log = state.event_log;
113        self.executed_bounded_actions = state.executed_bounded_actions;
114        self.extra = state.extra;
115    }
116
117    pub fn load_from_file(path: &str) -> io::Result<Self> {
118        let mut mesh = Self::new();
119        if Path::new(path).exists() {
120            let data = std::fs::read_to_string(path)?;
121            if let Ok(state) = serde_json::from_str::<AutonomicMeshState>(&data) {
122                mesh.load_state(state);
123            }
124        } else {
125            let mut lsp1 = LspInstance::new("LSP_1");
126            lsp1.phase = LspPhase::Initialized;
127            let mut lsp2 = LspInstance::new("LSP_2");
128            lsp2.phase = LspPhase::Initialized;
129            lsp2.policy_state = Some(PolicyState::Operational);
130
131            mesh.add_instance(lsp1);
132            mesh.add_instance(lsp2);
133            mesh.save_to_file(path)?;
134        }
135        mesh.register_hook(Box::new(IntakeDiagnosticHook));
136        mesh.register_hook(Box::new(IntakeClearHook));
137        mesh.register_hook(Box::new(OcelProcessHook::new()));
138        Ok(mesh)
139    }
140
141    pub fn save_to_file(&self, path: &str) -> io::Result<()> {
142        let state = self.to_state();
143        let serialized =
144            serde_json::to_string_pretty(&state).map_err(|e| io::Error::other(e.to_string()))?;
145        std::fs::write(path, serialized)?;
146        Ok(())
147    }
148
149    pub fn add_instance(&mut self, instance: LspInstance) {
150        self.instances.insert(instance.id.clone(), instance);
151    }
152
153    pub fn register_instance(&mut self, id: String) {
154        self.add_instance(LspInstance::new(&id));
155    }
156
157    pub fn register_hook(&mut self, hook: Box<dyn Hook>) {
158        self.hooks.push(hook);
159    }
160
161    pub fn hook_descriptors(&self) -> Vec<crate::runtime::mesh_types::HookDescriptor> {
162        self.hooks.iter().map(|h| h.descriptor()).collect()
163    }
164
165    pub fn dispatch_event(&mut self, event: HookEvent) {
166        if self.dispatch_depth >= MAX_DISPATCH_DEPTH {
167            self.event_log.push(HookEvent::DiagnosticEmitted {
168                instance_id: InstanceId::from("mesh"),
169                diagnostic: Box::new(MaxDiagnostic {
170                    lsp: lsp_types_max::Diagnostic {
171                        range: lsp_types_max::Range::default(),
172                        severity: Some(lsp_types_max::DiagnosticSeverity::ERROR),
173                        code: None,
174                        code_description: None,
175                        source: Some("lsp-max".to_string()),
176                        message: format!(
177                            "Dispatch depth {} exceeds limit {MAX_DISPATCH_DEPTH}; recursive hook chain terminated",
178                            self.dispatch_depth
179                        ),
180                        related_information: None,
181                        tags: None,
182                        data: None,
183                    },
184                    diagnostic_id: format!("dispatch-depth-exceeded-{}", self.dispatch_depth),
185                    law_id: "MESH_DISPATCH_DEPTH".to_string(),
186                    attempted_transition: None,
187                    violated_axes: vec!["recursion-safety".to_string()],
188                    doc_routes: vec![],
189                    repair_actions: vec![],
190                    verification_gates: vec![],
191                    receipt_obligation: None,
192                    law_axis: lsp_max_protocol::LawAxis::Security,
193                    violated_invariant: String::new(),
194                    observed_state: serde_json::Value::Null,
195                    expected_state: serde_json::Value::Null,
196                    repairability: lsp_max_protocol::Repairability::NotRepairable,
197                    terminality: lsp_max_protocol::Terminality::Terminal,
198                }),
199            });
200            return;
201        }
202        self.dispatch_depth += 1;
203
204        self.event_log.push(event.clone());
205        if self.event_log.len() > MAX_EVENT_LOG {
206            self.event_log.drain(..self.event_log.len() - MAX_EVENT_LOG);
207        }
208
209        let mut actions = Vec::new();
210        for hook in &self.hooks {
211            let triggered = hook.trigger(&event);
212            actions.extend(triggered);
213        }
214
215        for action in actions {
216            self.execute_action(action);
217        }
218
219        self.dispatch_depth = self.dispatch_depth.saturating_sub(1);
220    }
221
222    pub fn execute_action(&mut self, action: MeshAction) {
223        self.action_seq = self.action_seq.saturating_add(1);
224        let seq = self.action_seq;
225
226        let maybe_instance_id: Option<String> = match &action {
227            MeshAction::AddDiagnostic { instance_id, .. }
228            | MeshAction::ClearDiagnostic { instance_id, .. }
229            | MeshAction::TransitionPolicyState { instance_id, .. }
230            | MeshAction::EmitReceipt { instance_id, .. }
231            | MeshAction::ExecuteBoundedAction { instance_id, .. }
232            | MeshAction::ResetInstance { instance_id } => Some(instance_id.0.clone()),
233        };
234        let old_score: Option<f64> = maybe_instance_id
235            .as_deref()
236            .and_then(|id| self.instances.get(id))
237            .map(|inst| inst.conformance_score());
238
239        match action {
240            MeshAction::TransitionPolicyState {
241                instance_id,
242                new_state,
243            } => {
244                if let Some(instance) = self.instances.get_mut(&instance_id.0) {
245                    let old_state = instance
246                        .policy_state
247                        .clone()
248                        .unwrap_or(PolicyState::Operational);
249                    instance.policy_state = Some(new_state.clone());
250
251                    let event = HookEvent::PolicyStateChanged {
252                        instance_id,
253                        from_state: old_state,
254                        to_state: new_state,
255                    };
256                    self.dispatch_event(event);
257                }
258            }
259            MeshAction::ClearDiagnostic {
260                instance_id,
261                diagnostic_id,
262            } => {
263                if let Some(instance) = self.instances.get_mut(&instance_id.0) {
264                    let old_len = instance.diagnostics.len();
265                    instance
266                        .diagnostics
267                        .retain(|d| d.diagnostic_id != diagnostic_id);
268                    if instance.diagnostics.len() < old_len {
269                        instance.invalidate_score_cache();
270                        let event = HookEvent::DiagnosticCleared {
271                            instance_id,
272                            diagnostic_id,
273                        };
274                        self.dispatch_event(event);
275                    }
276                }
277            }
278            MeshAction::AddDiagnostic {
279                instance_id,
280                diagnostic,
281            } => {
282                if let Some(instance) = self.instances.get_mut(&instance_id.0) {
283                    instance.diagnostics.push((*diagnostic).clone());
284                    instance.invalidate_score_cache();
285                    let event = HookEvent::DiagnosticEmitted {
286                        instance_id,
287                        diagnostic,
288                    };
289                    self.dispatch_event(event);
290                }
291            }
292            MeshAction::EmitReceipt {
293                instance_id,
294                mut receipt,
295            } => {
296                if let Some(instance) = self.instances.get_mut(&instance_id.0) {
297                    receipt.hash = sha256(receipt.receipt_id.as_bytes());
298                    instance.receipts.push(receipt.clone());
299                    let event = HookEvent::ReceiptEmitted {
300                        instance_id,
301                        receipt,
302                    };
303                    self.dispatch_event(event);
304                }
305            }
306            MeshAction::ExecuteBoundedAction {
307                instance_id,
308                action_id,
309                description,
310            } => {
311                if action_id == "act-create-refund-receipt" {
312                    let receipt_dir =
313                        std::env::var("MESH_RECEIPT_DIR").unwrap_or_else(|_| ".".to_string());
314                    let file_path = Path::new(&receipt_dir).join("refund_receipt.txt");
315                    let content = format!(
316                        "REFUND RECEIPT\nInstance: {}\nDescription: {}\nStatus: Executed\nTimestamp: {}\n",
317                        instance_id,
318                        description,
319                        std::time::SystemTime::now()
320                            .duration_since(std::time::UNIX_EPOCH)
321                            .unwrap_or(std::time::Duration::ZERO)
322                            .as_secs()
323                    );
324                    if let Err(e) = std::fs::write(&file_path, content) {
325                        eprintln!(
326                            "warn: failed to write receipt to {}: {}",
327                            file_path.display(),
328                            e
329                        );
330                    }
331                }
332                self.dispatch_event(HookEvent::BoundedActionExecuted {
333                    instance_id,
334                    action_id: action_id.clone(),
335                    description: description.clone(),
336                });
337                self.executed_bounded_actions.push(action_id);
338            }
339            MeshAction::ResetInstance { instance_id } => {
340                if let Some(instance) = self.instances.get_mut(&instance_id.0) {
341                    instance.diagnostics.clear();
342                    instance.receipts.clear();
343                    instance.policy_state = Some(PolicyState::Operational);
344                    instance.invalidate_score_cache();
345                    self.dispatch_event(HookEvent::InstanceReset { instance_id });
346                }
347            }
348        }
349
350        if let Some(iid) = maybe_instance_id {
351            if let Some(new_score) = self
352                .instances
353                .get(&iid)
354                .map(|inst| inst.conformance_score())
355            {
356                if let Some(old) = old_score {
357                    if (new_score - old).abs() > f64::EPSILON {
358                        let entry = ConformanceDeltaEntry {
359                            seq,
360                            instance_id: iid,
361                            old_score: old,
362                            new_score,
363                            timestamp: String::new(),
364                        };
365                        self.conformance_delta_log.push_back(entry);
366                        if self.conformance_delta_log.len() > MAX_CONFORMANCE_DELTA_LOG {
367                            self.conformance_delta_log.pop_front();
368                        }
369                    }
370                }
371            }
372        }
373    }
374
375    pub fn run_command(&mut self, cmd: &str) -> Result<String, String> {
376        let parts: Vec<&str> = cmd.split_whitespace().collect();
377        if parts.is_empty() {
378            return Err("Empty command".to_string());
379        }
380
381        match parts[0] {
382            "diagnose" => {
383                if parts.len() < 6 {
384                    return Err(
385                        "Usage: diagnose <instance_id> <diag_id> <law_id> <severity> <msg...>"
386                            .to_string(),
387                    );
388                }
389                let instance_id = InstanceId::from(parts[1]);
390                let diag_id = parts[2].to_string();
391                let law_id = parts[3].to_string();
392                let severity_str = parts[4];
393                let msg = parts[5..].join(" ");
394
395                let severity = match severity_str {
396                    "error" => Some(lsp_types_max::DiagnosticSeverity::ERROR),
397                    "warning" => Some(lsp_types_max::DiagnosticSeverity::WARNING),
398                    "info" => Some(lsp_types_max::DiagnosticSeverity::INFORMATION),
399                    "hint" => Some(lsp_types_max::DiagnosticSeverity::HINT),
400                    _ => return Err(format!("Unknown severity: {}", severity_str)),
401                };
402
403                let diagnostic = MaxDiagnostic {
404                    lsp: lsp_types_max::Diagnostic {
405                        range: lsp_types_max::Range::default(),
406                        severity,
407                        code: None,
408                        code_description: None,
409                        source: Some("autonomic-mesh".to_string()),
410                        message: msg,
411                        related_information: None,
412                        tags: None,
413                        data: None,
414                    },
415                    diagnostic_id: diag_id,
416                    law_id,
417                    attempted_transition: None,
418                    violated_axes: vec!["semantic".to_string()],
419                    doc_routes: vec![],
420                    repair_actions: vec![],
421                    verification_gates: vec![],
422                    receipt_obligation: None,
423                    law_axis: lsp_max_protocol::LawAxis::Domain,
424                    violated_invariant: String::new(),
425                    observed_state: serde_json::Value::Null,
426                    expected_state: serde_json::Value::Null,
427                    repairability: lsp_max_protocol::Repairability::Unknown,
428                    terminality: lsp_max_protocol::Terminality::NonTerminal,
429                };
430
431                self.execute_action(MeshAction::AddDiagnostic {
432                    instance_id: instance_id.clone(),
433                    diagnostic: Box::new(diagnostic),
434                });
435
436                Ok(format!("Emitted diagnostic on {}", instance_id))
437            }
438            "clear" => {
439                if parts.len() < 3 {
440                    return Err("Usage: clear <instance_id> <diag_id>".to_string());
441                }
442                let instance_id = InstanceId::from(parts[1]);
443                let diag_id = parts[2].to_string();
444
445                self.execute_action(MeshAction::ClearDiagnostic {
446                    instance_id: instance_id.clone(),
447                    diagnostic_id: diag_id,
448                });
449
450                Ok(format!("Cleared diagnostic on {}", instance_id))
451            }
452            "state" => {
453                if parts.len() < 2 {
454                    return Err("Usage: state <instance_id>".to_string());
455                }
456                let instance_id = parts[1];
457                if let Some(inst) = self.instances.get(instance_id) {
458                    let policy_str = match &inst.policy_state {
459                        Some(p) => format!("{:?}", p),
460                        None => "None".to_string(),
461                    };
462                    Ok(format!(
463                        "Instance: {} | Phase: {} | Conformance: {} | PolicyState: {} | Diags: {} | Receipts: {}",
464                        inst.id,
465                        inst.phase,
466                        inst.conformance_score(),
467                        policy_str,
468                        inst.diagnostics.len(),
469                        inst.receipts.len()
470                    ))
471                } else {
472                    Err(format!("Instance not found: {}", instance_id))
473                }
474            }
475            "patch" => {
476                if parts.len() < 3 {
477                    return Err("Usage: patch <instance_id> <policy_state>".to_string());
478                }
479                let instance_id = InstanceId::from(parts[1]);
480                let new_state = parts[2].parse::<PolicyState>()?;
481
482                self.execute_action(MeshAction::TransitionPolicyState {
483                    instance_id: instance_id.clone(),
484                    new_state,
485                });
486
487                Ok(format!("Patched state on {}", instance_id))
488            }
489            _ => Err(format!("Unknown command: {}", parts[0])),
490        }
491    }
492}