clawft_kernel/
eml_persistence.rs1use std::path::{Path, PathBuf};
24
25use tracing::{debug, info, warn};
26
27use crate::eml_coherence::EmlCoherenceModel;
28use crate::eml_kernel::{
29 ComplexityModel, DeadLetterModel, GossipTimingModel, GovernanceScorerModel,
30 HealthThresholdModel, RestartStrategyModel,
31};
32
33pub fn eml_models_dir(runtime_dir: &Path) -> PathBuf {
42 runtime_dir.join(".weftos").join("eml-models")
43}
44
45#[derive(Default)]
55pub struct KernelEmlModels {
56 pub coherence: EmlCoherenceModel,
57 pub governance_scorer: GovernanceScorerModel,
58 pub restart_strategy: RestartStrategyModel,
59 pub health_threshold: HealthThresholdModel,
60 pub dead_letter: DeadLetterModel,
61 pub gossip_timing: GossipTimingModel,
62 pub complexity: ComplexityModel,
63}
64
65fn save_model<T: serde::Serialize>(dir: &Path, filename: &str, model: &T) -> std::io::Result<()> {
71 let path = dir.join(filename);
72 let json = serde_json::to_string_pretty(model)
73 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
74 std::fs::write(&path, json)?;
75 debug!(path = %path.display(), "saved EML model");
76 Ok(())
77}
78
79fn load_model<T: serde::de::DeserializeOwned>(dir: &Path, filename: &str) -> Option<T> {
83 let path = dir.join(filename);
84 if !path.exists() {
85 return None;
86 }
87 match std::fs::read_to_string(&path) {
88 Ok(data) => match serde_json::from_str::<T>(&data) {
89 Ok(model) => {
90 debug!(path = %path.display(), "loaded EML model");
91 Some(model)
92 }
93 Err(e) => {
94 warn!(path = %path.display(), error = %e, "failed to deserialize EML model, using defaults");
95 None
96 }
97 },
98 Err(e) => {
99 warn!(path = %path.display(), error = %e, "failed to read EML model file");
100 None
101 }
102 }
103}
104
105pub fn save_all(dir: &Path, models: &KernelEmlModels) -> std::io::Result<()> {
113 std::fs::create_dir_all(dir)?;
114
115 save_model(dir, "coherence.json", &models.coherence)?;
116 save_model(dir, "governance_scorer.json", &models.governance_scorer)?;
117 save_model(dir, "restart_strategy.json", &models.restart_strategy)?;
118 save_model(dir, "health_threshold.json", &models.health_threshold)?;
119 save_model(dir, "dead_letter.json", &models.dead_letter)?;
120 save_model(dir, "gossip_timing.json", &models.gossip_timing)?;
121 save_model(dir, "complexity.json", &models.complexity)?;
122
123 info!(dir = %dir.display(), "saved all kernel EML models");
124 Ok(())
125}
126
127pub fn load_all(dir: &Path) -> KernelEmlModels {
131 if !dir.exists() {
132 info!(dir = %dir.display(), "EML model directory does not exist, using defaults");
133 return KernelEmlModels::default();
134 }
135
136 let models = KernelEmlModels {
137 coherence: load_model(dir, "coherence.json").unwrap_or_default(),
138 governance_scorer: load_model(dir, "governance_scorer.json").unwrap_or_default(),
139 restart_strategy: load_model(dir, "restart_strategy.json").unwrap_or_default(),
140 health_threshold: load_model(dir, "health_threshold.json").unwrap_or_default(),
141 dead_letter: load_model(dir, "dead_letter.json").unwrap_or_default(),
142 gossip_timing: load_model(dir, "gossip_timing.json").unwrap_or_default(),
143 complexity: load_model(dir, "complexity.json").unwrap_or_default(),
144 };
145
146 info!(dir = %dir.display(), "loaded all kernel EML models");
147 models
148}
149
150#[cfg(test)]
155mod tests {
156 use super::*;
157
158 #[test]
159 fn save_and_load_roundtrip() {
160 let dir = std::env::temp_dir().join("eml_persistence_test");
161 let _ = std::fs::remove_dir_all(&dir);
162
163 let models = KernelEmlModels::default();
164 save_all(&dir, &models).expect("save should succeed");
165
166 let loaded = load_all(&dir);
167
168 assert!(!loaded.coherence.is_trained());
170 assert!(!loaded.governance_scorer.is_trained());
171 assert!(!loaded.restart_strategy.is_trained());
172 assert!(!loaded.health_threshold.is_trained());
173 assert!(!loaded.dead_letter.is_trained());
174 assert!(!loaded.gossip_timing.is_trained());
175 assert!(!loaded.complexity.is_trained());
176
177 let _ = std::fs::remove_dir_all(&dir);
178 }
179
180 #[test]
181 fn load_missing_dir_returns_defaults() {
182 let dir = std::env::temp_dir().join("eml_persistence_missing");
183 let _ = std::fs::remove_dir_all(&dir);
184
185 let models = load_all(&dir);
186 assert!(!models.coherence.is_trained());
187 }
188
189 #[test]
190 fn load_corrupt_file_returns_default() {
191 let dir = std::env::temp_dir().join("eml_persistence_corrupt");
192 let _ = std::fs::remove_dir_all(&dir);
193 std::fs::create_dir_all(&dir).unwrap();
194 std::fs::write(dir.join("coherence.json"), "not valid json").unwrap();
195
196 let models = load_all(&dir);
197 assert!(!models.coherence.is_trained());
198
199 let _ = std::fs::remove_dir_all(&dir);
200 }
201}