Skip to main content

lean_ctx/core/shadow/
persistence.rs

1//! JSON persistence and retention for Shadow Mode reports.
2use super::{ShadowReport, runtime::load_shadow_config};
3use chrono::Utc;
4use std::path::{Path, PathBuf};
5
6pub fn persist_report(report: &ShadowReport) -> std::io::Result<PathBuf> {
7    let dir = report_dir();
8    std::fs::create_dir_all(&dir)?;
9    let path = dir.join(format!(
10        "shadow_report_{}.json",
11        Utc::now().format("%Y%m%d_%H%M%S")
12    ));
13    std::fs::write(
14        &path,
15        serde_json::to_vec_pretty(report).map_err(std::io::Error::other)?,
16    )?;
17    for old in list_reports_in(&dir).into_iter().rev().skip(50) {
18        std::fs::remove_file(old)?;
19    }
20    Ok(path)
21}
22pub fn list_reports() -> Vec<PathBuf> {
23    list_reports_in(&report_dir())
24}
25pub fn load_report(path: &Path) -> Option<ShadowReport> {
26    std::fs::read(path)
27        .ok()
28        .and_then(|body| serde_json::from_slice(&body).ok())
29}
30fn report_dir() -> PathBuf {
31    let configured = load_shadow_config().report_dir;
32    if configured.trim().is_empty() {
33        crate::core::paths::state_dir()
34            .map(|d| d.join("shadow_reports"))
35            .unwrap_or_else(|_| PathBuf::from("shadow_reports"))
36    } else {
37        PathBuf::from(configured)
38    }
39}
40fn list_reports_in(dir: &Path) -> Vec<PathBuf> {
41    let mut paths = std::fs::read_dir(dir)
42        .into_iter()
43        .flatten()
44        .flatten()
45        .map(|e| e.path())
46        .filter(|p| p.extension().is_some_and(|e| e == "json"))
47        .collect::<Vec<_>>();
48    paths.sort();
49    paths
50}