use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArtifactKind {
Config,
Memory,
Skills,
Goals,
Sessions,
Secrets,
Logs,
Runtime,
Cache,
}
impl ArtifactKind {
pub fn as_str(&self) -> &'static str {
match self {
ArtifactKind::Config => "config",
ArtifactKind::Memory => "memory",
ArtifactKind::Skills => "skills",
ArtifactKind::Goals => "goals",
ArtifactKind::Sessions => "sessions",
ArtifactKind::Secrets => "secrets",
ArtifactKind::Logs => "logs",
ArtifactKind::Runtime => "runtime",
ArtifactKind::Cache => "cache",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Root {
ConfigDir,
}
impl Root {
pub fn base(&self) -> PathBuf {
match self {
Root::ConfigDir => crate::config::config_dir(),
}
}
pub fn as_str(&self) -> &'static str {
match self {
Root::ConfigDir => "config_dir",
}
}
}
#[derive(Debug, Clone)]
pub struct Artifact {
pub name: &'static str,
pub rel: &'static str,
pub root: Root,
pub path: PathBuf,
pub kind: ArtifactKind,
pub durable: bool,
pub sensitive: bool,
pub description: &'static str,
}
impl Artifact {
pub fn exists(&self) -> bool {
self.path.exists()
}
}
type Row = (&'static str, &'static str, Root, ArtifactKind, bool, bool, &'static str);
const ROWS: &[Row] = &[
("config", "config.toml", Root::ConfigDir, ArtifactKind::Config, true, false, "User configuration (may contain api_key)"),
("soul", "SOUL.md", Root::ConfigDir, ArtifactKind::Config, true, false, "User identity / persona"),
("filters", "filters.toml", Root::ConfigDir, ArtifactKind::Config, true, false, "Output filter rules"),
("tools", "tools.d", Root::ConfigDir, ArtifactKind::Config, true, false, "Custom tool scripts"),
("peers_trust", "peers_trust.toml", Root::ConfigDir, ArtifactKind::Config, true, false, "A2A peer trust levels"),
("memory", "memory", Root::ConfigDir, ArtifactKind::Memory, true, false, "Main memory graph"),
("mempalace", "mempalace", Root::ConfigDir, ArtifactKind::Memory, true, false, "Mempalace memory taxonomy"),
("strategies", "strategies", Root::ConfigDir, ArtifactKind::Skills, true, false, "Strategies + skills"),
("goals", "goals", Root::ConfigDir, ArtifactKind::Goals, true, false, "Long-term goals"),
("sessions", "sessions.db", Root::ConfigDir, ArtifactKind::Sessions, true, false, "Session history database"),
("imports", "imports", Root::ConfigDir, ArtifactKind::Cache, false, false, "Imported external sessions"),
("overnight", "overnight", Root::ConfigDir, ArtifactKind::Cache, false, false, "Overnight run records"),
("backups", "backups", Root::ConfigDir, ArtifactKind::Cache, false, false, "Backup output dir (not self-backed-up)"),
("logs", "logs", Root::ConfigDir, ArtifactKind::Logs, false, false, "Run + audit logs"),
("trash", "trash", Root::ConfigDir, ArtifactKind::Runtime, false, false, "Recoverable-delete trash"),
("snapshots", "snapshots", Root::ConfigDir, ArtifactKind::Runtime, false, false, "Working-dir snapshots"),
("bin", "bin", Root::ConfigDir, ArtifactKind::Runtime, false, false, "PATH shim dir (rm→trash)"),
("gateway_sock", "gateway.sock", Root::ConfigDir, ArtifactKind::Runtime, false, false, "Gateway control socket (transient)"),
("gateway_lock", "gateway.lock", Root::ConfigDir, ArtifactKind::Runtime, false, false, "Single-instance lock (transient)"),
("readline_history", "readline_history", Root::ConfigDir, ArtifactKind::Runtime, false, false, "REPL input history"),
("swap_state", "swap-state.json", Root::ConfigDir, ArtifactKind::Runtime, false, false, "Self-upgrade swap state"),
("update_check", "update_check.json", Root::ConfigDir, ArtifactKind::Cache, false, false, "Update-check cache"),
("secrets", "secrets.json", Root::ConfigDir, ArtifactKind::Secrets, true, true, "Credential vault (excluded from backup by default)"),
("peers", "peers.json", Root::ConfigDir, ArtifactKind::Config, true, false, "A2A peers"),
("remotes", "remotes.json", Root::ConfigDir, ArtifactKind::Config, true, true, "SSH remotes (may contain passwords)"),
("checkpoints", "checkpoints", Root::ConfigDir, ArtifactKind::Runtime, false, false, "Pre-write file checkpoints"),
("wal", "wal", Root::ConfigDir, ArtifactKind::Memory, false, false, "Memory write-ahead log"),
("intervene", "intervene.txt", Root::ConfigDir, ArtifactKind::Runtime, false, false, "Channel intervention file"),
("keyinfo", "keyinfo.txt", Root::ConfigDir, ArtifactKind::Runtime, false, false, "Channel key-info file"),
];
pub fn all() -> Vec<Artifact> {
ROWS.iter()
.map(|&(name, rel, root, kind, durable, sensitive, description)| Artifact {
name,
rel,
root,
path: root.base().join(rel),
kind,
durable,
sensitive,
description,
})
.collect()
}
pub fn durable(include_secrets: bool) -> Vec<Artifact> {
all()
.into_iter()
.filter(|a| a.durable && (include_secrets || !a.sensitive))
.collect()
}
pub fn by_kind(kind: ArtifactKind) -> Vec<Artifact> {
all().into_iter().filter(|a| a.kind == kind).collect()
}
#[derive(Debug, Clone)]
pub struct ExternalArtifact {
pub name: &'static str,
pub path: PathBuf,
pub present: bool,
pub description: String,
}
pub fn external_probe() -> Vec<ExternalArtifact> {
let mut out = Vec::new();
if let Some(home) = dirs_next::home_dir() {
let user_unit = home.join(".config/systemd/user/aegis-gateway.service");
let present = user_unit.exists();
out.push(ExternalArtifact {
name: "systemd_user_unit",
path: user_unit,
present,
description: "user gateway service — remove with `aegis gateway uninstall`".to_string(),
});
}
let sys_unit = PathBuf::from("/etc/systemd/system/aegis-gateway.service");
let sys_present = sys_unit.exists();
out.push(ExternalArtifact {
name: "systemd_system_unit",
path: sys_unit,
present: sys_present,
description: "system gateway service — remove with `sudo aegis gateway uninstall --system`"
.to_string(),
});
if let Ok(exe) = std::env::current_exe() {
out.push(ExternalArtifact {
name: "binary",
path: exe.clone(),
present: exe.exists(),
description: "the aegis binary itself".to_string(),
});
}
let tmp = std::env::temp_dir();
if let Ok(rd) = std::fs::read_dir(&tmp) {
for e in rd.flatten() {
let name = e.file_name();
let name = name.to_string_lossy();
if name.starts_with("aegis-wt-") {
out.push(ExternalArtifact {
name: "git_worktree",
path: e.path(),
present: true,
description: "leftover sub-agent git worktree — `git worktree remove` in the source repo"
.to_string(),
});
}
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_rows_resolve_absolute_paths() {
for a in all() {
assert!(a.path.is_absolute() || a.path.starts_with("."), "{}", a.name);
assert!(!a.name.is_empty());
assert!(!a.rel.is_empty());
}
}
#[test]
fn names_are_unique() {
let mut names: Vec<&str> = all().iter().map(|a| a.name).collect();
names.sort_unstable();
let before = names.len();
names.dedup();
assert_eq!(before, names.len(), "artifact names must be unique");
}
#[test]
fn durable_excludes_sensitive_by_default() {
let without = durable(false);
assert!(without.iter().all(|a| !a.sensitive));
assert!(without.iter().any(|a| a.name == "memory"));
assert!(!without.iter().any(|a| a.name == "secrets"));
let with = durable(true);
assert!(with.iter().any(|a| a.name == "secrets"));
}
#[test]
fn durable_excludes_transient() {
let d = durable(true);
assert!(!d.iter().any(|a| a.name == "trash"));
assert!(!d.iter().any(|a| a.name == "gateway_sock"));
assert!(!d.iter().any(|a| a.name == "update_check"));
}
#[test]
fn by_kind_groups_correctly() {
let mem = by_kind(ArtifactKind::Memory);
assert!(mem.iter().any(|a| a.name == "memory"));
assert!(mem.iter().any(|a| a.name == "mempalace"));
assert!(mem.iter().any(|a| a.name == "wal"));
let skills = by_kind(ArtifactKind::Skills);
assert_eq!(skills.len(), 1);
assert_eq!(skills[0].name, "strategies");
}
#[test]
fn all_under_config_dir_after_unification() {
assert!(all().iter().all(|a| a.root == Root::ConfigDir));
}
#[test]
fn external_probe_includes_binary_and_units() {
let ext = external_probe();
assert!(ext.iter().any(|e| e.name == "binary"));
assert!(ext.iter().any(|e| e.name == "systemd_system_unit"));
}
}