From c188d9de78e4d62e674e97b70a86b745f738e886 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sun, 23 Aug 2026 07:59:40 -0400 Subject: [PATCH] fix(lifecycle): abort unsafe declarative uninstall --- core/archipelago/src/api/rpc/package/runtime.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/core/archipelago/src/api/rpc/package/runtime.rs b/core/archipelago/src/api/rpc/package/runtime.rs index 83ad0b5e..292d0bda 100644 --- a/core/archipelago/src/api/rpc/package/runtime.rs +++ b/core/archipelago/src/api/rpc/package/runtime.rs @@ -365,8 +365,18 @@ impl RpcHandler { // after uninstall. The reconciler owns a manifest map independent of // podman state, so a raw `podman rm` alone is not enough. if let Some(orchestrator) = &self.orchestrator { + let mut teardown_errors = Vec::new(); for app_id in orchestrator_uninstall_app_ids(package_id) { - let _ = orchestrator.remove(&app_id, preserve_data).await; + if let Err(err) = orchestrator.remove(&app_id, preserve_data).await { + teardown_errors.push(format!("{app_id}: {err:#}")); + } + } + if !teardown_errors.is_empty() { + return Err(anyhow::anyhow!( + "Uninstall {} aborted: failed to remove declarative app unit(s): {}", + package_id, + teardown_errors.join("; ") + )); } } @@ -2182,6 +2192,11 @@ mod tests { assert!(!is_missing_container_error("Error: OCI runtime error")); } + #[test] + fn single_app_uninstall_targets_its_declarative_unit() { + assert_eq!(orchestrator_uninstall_app_ids("cuprate"), vec!["cuprate"]); + } + #[test] fn runtime_host_ports_are_manifest_derived_for_public_apps() { assert_eq!(runtime_host_ports("photoprism"), vec![2342]);