feat: phase 2 — audit config + update --rollback (closes #63 #64)

Axe 8 (sécurité) :
- am audit : checksums SHA-256 des configs (embarquée, utilisateur,
  locale) stockés dans state.json, croisement avec events.jsonl pour
  dater les changements, détection des modifications manuelles hors de
  am (statut MANUAL), rapport lisible + --json stable (contrat)
- backup automatique avant chaque am update (agent ou --all) : état +
  config utilisateur + répertoires d'installation + shims dans
  <state>/backups/<ts>/ avec manifest.json
- am update --rollback [point] : restaure le dernier backup (ou un id
  précis, préfixe accepté), arrête les agents concernés d'abord,
  événement EventKind::Rollback journalisé
- am update --rollback list : liste les points de restauration
- rétention settings.backups_keep (défaut 5) appliquée après chaque backup
- événements EventKind::Backup / EventKind::Rollback pour l'audit

REPL : commande audit + update --rollback (parse, complétion), help specs,
tip cheat sheet, man pages régénérées (50 pages). Version 0.4.6,
ROADMAP cases cochées, 272 tests verts.
This commit is contained in:
2026-08-18 09:44:16 -04:00
parent 22bc529818
commit d9ffc34156
21 changed files with 971 additions and 33 deletions
+16 -2
View File
@@ -109,6 +109,7 @@ const COMMAND_DESCRIPTIONS: &[(&str, &str)] = &[
("models", "list local models (ollama, llama.cpp, LM Studio)"),
("catalog", "manage remote catalogs"),
("suggest", "recommend an agent for a request"),
("audit", "who changed what, when (config checksums)"),
("shell", "show or switch the system shell"),
("theme", "show or switch the color theme"),
("tip", "cheat sheet of the most useful commands"),
@@ -222,7 +223,7 @@ impl AmCompleter {
"start", "stop", "restart", "run", "doctor", "config", "completion",
"self-update", "self-uninstall", "export", "import", "shell", "theme",
"tip", "dashboard", "favorite", "unfavorite", "note", "tag", "untag", "tags",
"profile", "man", "models", "catalog", "suggest",
"profile", "man", "models", "catalog", "suggest", "audit",
"ls", "dir", "cd", "ps", "where", "get", "help", "version", "exit",
],
config_sub: vec!["show", "path", "edit", "validate", "add"],
@@ -769,7 +770,7 @@ pub fn banner_box(
" config alias · secret · profile · config · doctor · completion · man · tip".to_string(),
));
rows.push(inner(
" models models · models --prune · catalog · suggest".to_string(),
" models models · models --prune · catalog · suggest · audit".to_string(),
));
rows.push(inner(
" system self-update · self-uninstall · export · import".to_string(),
@@ -1608,14 +1609,23 @@ fn handle_line(
Command::Update {
agent: None,
all: true,
rollback: opt_value("--rollback"),
}
} else if let Some(rb) = opt_value("--rollback") {
Command::Update {
agent: None,
all: false,
rollback: Some(rb),
}
} else {
Command::Update {
agent: rest.first().cloned(),
all: false,
rollback: None,
}
}
}
"audit" => Command::Audit,
"doctor" => Command::Doctor { fix: flag("--fix") },
"run" => {
let agent = need("agent name")?;
@@ -1730,6 +1740,10 @@ fn is_am_command(word: &str) -> bool {
| "profile"
| "man"
| "tip"
| "models"
| "catalog"
| "suggest"
| "audit"
)
}