use std::path::{Path, PathBuf};
use tracing::{debug, info, warn};
use crate::eml_coherence::EmlCoherenceModel;
use crate::eml_kernel::{
ComplexityModel, DeadLetterModel, GovernanceScorerModel, GossipTimingModel,
HealthThresholdModel, RestartStrategyModel,
};
pub fn eml_models_dir(runtime_dir: &Path) -> PathBuf {
runtime_dir.join(".weftos").join("eml-models")
}
pub struct KernelEmlModels {
pub coherence: EmlCoherenceModel,
pub governance_scorer: GovernanceScorerModel,
pub restart_strategy: RestartStrategyModel,
pub health_threshold: HealthThresholdModel,
pub dead_letter: DeadLetterModel,
pub gossip_timing: GossipTimingModel,
pub complexity: ComplexityModel,
}
impl Default for KernelEmlModels {
fn default() -> Self {
Self {
coherence: EmlCoherenceModel::new(),
governance_scorer: GovernanceScorerModel::new(),
restart_strategy: RestartStrategyModel::new(),
health_threshold: HealthThresholdModel::new(),
dead_letter: DeadLetterModel::new(),
gossip_timing: GossipTimingModel::new(),
complexity: ComplexityModel::new(),
}
}
}
fn save_model<T: serde::Serialize>(dir: &Path, filename: &str, model: &T) -> std::io::Result<()> {
let path = dir.join(filename);
let json = serde_json::to_string_pretty(model)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
std::fs::write(&path, json)?;
debug!(path = %path.display(), "saved EML model");
Ok(())
}
fn load_model<T: serde::de::DeserializeOwned>(dir: &Path, filename: &str) -> Option<T> {
let path = dir.join(filename);
if !path.exists() {
return None;
}
match std::fs::read_to_string(&path) {
Ok(data) => match serde_json::from_str::<T>(&data) {
Ok(model) => {
debug!(path = %path.display(), "loaded EML model");
Some(model)
}
Err(e) => {
warn!(path = %path.display(), error = %e, "failed to deserialize EML model, using defaults");
None
}
},
Err(e) => {
warn!(path = %path.display(), error = %e, "failed to read EML model file");
None
}
}
}
pub fn save_all(dir: &Path, models: &KernelEmlModels) -> std::io::Result<()> {
std::fs::create_dir_all(dir)?;
save_model(dir, "coherence.json", &models.coherence)?;
save_model(dir, "governance_scorer.json", &models.governance_scorer)?;
save_model(dir, "restart_strategy.json", &models.restart_strategy)?;
save_model(dir, "health_threshold.json", &models.health_threshold)?;
save_model(dir, "dead_letter.json", &models.dead_letter)?;
save_model(dir, "gossip_timing.json", &models.gossip_timing)?;
save_model(dir, "complexity.json", &models.complexity)?;
info!(dir = %dir.display(), "saved all kernel EML models");
Ok(())
}
pub fn load_all(dir: &Path) -> KernelEmlModels {
if !dir.exists() {
info!(dir = %dir.display(), "EML model directory does not exist, using defaults");
return KernelEmlModels::default();
}
let models = KernelEmlModels {
coherence: load_model(dir, "coherence.json").unwrap_or_default(),
governance_scorer: load_model(dir, "governance_scorer.json").unwrap_or_default(),
restart_strategy: load_model(dir, "restart_strategy.json").unwrap_or_default(),
health_threshold: load_model(dir, "health_threshold.json").unwrap_or_default(),
dead_letter: load_model(dir, "dead_letter.json").unwrap_or_default(),
gossip_timing: load_model(dir, "gossip_timing.json").unwrap_or_default(),
complexity: load_model(dir, "complexity.json").unwrap_or_default(),
};
info!(dir = %dir.display(), "loaded all kernel EML models");
models
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn save_and_load_roundtrip() {
let dir = std::env::temp_dir().join("eml_persistence_test");
let _ = std::fs::remove_dir_all(&dir);
let models = KernelEmlModels::default();
save_all(&dir, &models).expect("save should succeed");
let loaded = load_all(&dir);
assert!(!loaded.coherence.is_trained());
assert!(!loaded.governance_scorer.is_trained());
assert!(!loaded.restart_strategy.is_trained());
assert!(!loaded.health_threshold.is_trained());
assert!(!loaded.dead_letter.is_trained());
assert!(!loaded.gossip_timing.is_trained());
assert!(!loaded.complexity.is_trained());
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn load_missing_dir_returns_defaults() {
let dir = std::env::temp_dir().join("eml_persistence_missing");
let _ = std::fs::remove_dir_all(&dir);
let models = load_all(&dir);
assert!(!models.coherence.is_trained());
}
#[test]
fn load_corrupt_file_returns_default() {
let dir = std::env::temp_dir().join("eml_persistence_corrupt");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("coherence.json"), "not valid json").unwrap();
let models = load_all(&dir);
assert!(!models.coherence.is_trained());
let _ = std::fs::remove_dir_all(&dir);
}
}