Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

crates/autonom/src/allowlist.rs

//! The checked-in **allowlist / excuse register** file model — `autonom-allow.toml`.
//!
//! The pure [`Allowlist`]/[`AllowEntry`] *types* live in
//! [`nornir_testmatrix::coverage`]; this module is the **file seam**: the
//! canonical path, the TOML load (empty when absent), and the burn-down save.
//!
//! Contract (unchanged from nornir's original home): the file is SEEDED by
//! `--seed-allowlist` with every currently-uncovered surface node, and is BURNED
//! DOWN by deleting an entry once a real inject-assert test covers that surface.
//! A STALE entry (now-covered or surface-gone) FAILS the gate — enforced upstream
//! by [`nornir_testmatrix::coverage::stale_allowlist_entries`], not here.

use std::path::{Path, PathBuf};

use anyhow::{Context, Result};

// Consume the pure allowlist types through autonom's OWN façade (`crate::…`), not
// `nornir_testmatrix::coverage::…` directly — the file seam dogfoods the re-export
// like feeder/report/bench do, so a gap in the façade is a compile error here too.
pub use crate::{AllowEntry, Allowlist};

/// The canonical allowlist path for a repo: `<repo_root>/.nornir/autonom-allow.toml`.
pub fn allowlist_path(repo_root: &Path) -> PathBuf {
    repo_root.join(".nornir").join("autonom-allow.toml")
}

/// Load the checked-in allowlist (empty if the file is absent).
pub fn load_allowlist(path: &Path) -> Result<Allowlist> {
    if !path.exists() {
        return Ok(Allowlist::new());
    }
    let text = std::fs::read_to_string(path)
        .with_context(|| format!("read allowlist {}", path.display()))?;
    let al: Allowlist =
        toml::from_str(&text).with_context(|| format!("parse allowlist {}", path.display()))?;
    Ok(al)
}

/// Write the allowlist as a checked-in `autonom-allow.toml` (the `--seed`/burn-down
/// file). Creates the `.nornir/` dir if needed.
pub fn save_allowlist(path: &Path, allowlist: &Allowlist) -> Result<()> {
    if let Some(dir) = path.parent() {
        std::fs::create_dir_all(dir).with_context(|| format!("create dir {}", dir.display()))?;
    }
    let header = "# autonom-allow.toml — the completeness-gate allowlist.\n\
        # SEEDED by `nornir test coverage --seed-allowlist` with every currently-\n\
        # uncovered surface node. BURN IT DOWN: delete an entry once a real\n\
        # inject-assert test covers that surface. A STALE entry (now-covered or\n\
        # surface-gone) FAILS the gate — the excuse must not outlive its surface.\n\n";
    let body = toml::to_string_pretty(allowlist)
        .with_context(|| format!("serialize allowlist {}", path.display()))?;
    std::fs::write(path, format!("{header}{body}"))
        .with_context(|| format!("write allowlist {}", path.display()))?;
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn absent_file_is_empty_allowlist() {
        let p = Path::new("/nonexistent/.nornir/autonom-allow.toml");
        let al = load_allowlist(p).unwrap();
        assert!(al.key_set().is_empty());
    }

    #[test]
    fn path_is_under_dot_nornir() {
        let p = allowlist_path(Path::new("/repo"));
        assert!(p.ends_with(".nornir/autonom-allow.toml"));
    }

    #[test]
    fn save_then_load_round_trips() {
        let dir = std::env::temp_dir().join(format!("autonom-al-{}", std::process::id()));
        let p = dir.join(".nornir").join("autonom-allow.toml");
        let mut al = Allowlist::new();
        al.entries.push(AllowEntry {
            key: "viz_tab:Bench@fat".into(),
            reason: "TODO(autonom): wire it".into(),
        });
        save_allowlist(&p, &al).unwrap();
        let back = load_allowlist(&p).unwrap();
        assert!(back.key_set().contains("viz_tab:Bench@fat"));
        let _ = std::fs::remove_dir_all(&dir);
    }
}