hammer-upgrade: journal de intención + recover idempotente (E4 endurecimiento #4a)
El apply escribe el plan completo a pending.json ANTES de proyectar y lo limpia al commitear; un corte a media proyección deja pending.json + FHS a medias. La proyección se hizo re-entrante (project_plan, backups idempotentes via backup_existing_once que nunca pisa el original) ⇒ recover COMPLETA (roll-forward, re-verifica of_tree) o DESHACE (roll-back: restaura backups, borra la gen a medias, current->padre). apply se niega con PendingExists si hay intento; status lo avisa. CLI: hammer upgrade recover [--rollback]. 5 tests (corte a media proyeccion -> ambos modos) + ejercicio en upgrade-e2e-test.sh. 14 tests verde. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -275,6 +275,12 @@ enum UpgradeCmd {
|
||||
},
|
||||
/// Revierte la generación viva al árbol anterior (o al estado pre-upgrades).
|
||||
Rollback,
|
||||
/// Completa (o deshace con --rollback) un apply interrumpido por un corte/reinicio (intento pendiente).
|
||||
Recover {
|
||||
/// Deshace el apply a medias (vuelve al estado pre-apply) en vez de completarlo.
|
||||
#[arg(long)]
|
||||
rollback: bool,
|
||||
},
|
||||
/// Muestra la generación viva y el historial.
|
||||
Status,
|
||||
/// Borra generaciones que el rollback ya no alcanza (huérfanas tras un rollback). Recupera espacio.
|
||||
@@ -865,7 +871,21 @@ fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
println!(" {} restaurados, {} borrados", r.restored.len(), r.deleted.len());
|
||||
}
|
||||
UpgradeCmd::Recover { rollback } => {
|
||||
let j = hammer_journal::Journal::open(&journal)?;
|
||||
let r = hammer_upgrade::recover(&store, &root, &state_root, Some(&j), rollback)?;
|
||||
if !r.recovered {
|
||||
println!("no hay apply interrumpido — nada que recuperar");
|
||||
} else if r.rolled_forward {
|
||||
println!("✓ apply interrumpido COMPLETADO (generación {})", r.generation);
|
||||
} else {
|
||||
println!("✓ apply interrumpido DESHECHO (generación {} descartada)", r.generation);
|
||||
}
|
||||
}
|
||||
UpgradeCmd::Status => {
|
||||
if let Some(p) = hammer_upgrade::pending(&state_root)? {
|
||||
println!("⚠ apply interrumpido pendiente (generación {}) — corré `hammer upgrade recover [--rollback]`", p.id);
|
||||
}
|
||||
let cur = hammer_upgrade::current(&state_root)?;
|
||||
match cur {
|
||||
Some(id) => println!("generación viva: {id}"),
|
||||
|
||||
Reference in New Issue
Block a user