controller v0.266.0: a failed install removes what it started (R-649, operator ruling)
gates / gates (push) Successful in 27s

compose down (volumes kept) before the record reads not deployed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
2026-09-23 18:06:29 +02:00
parent 411a43f8ef
commit 964ae7538f
4 changed files with 97 additions and 0 deletions
+11
View File
@@ -422,6 +422,17 @@ func (m *Manager) runComposeDeploy(name, stackDir string, env map[string]string,
if composeErr != nil {
m.logger.Printf("[ERROR] [stacks] Stack %s deploy failed after %.1fs: %v", name, time.Since(start).Seconds(), composeErr)
// R-649 (v0.266.0, operator ruling 2026-09-23): a failed install REMOVES what it started, so
// „not installed" never stands over running containers (R-634's promise, for the deploy's OWN
// failures — e.g. a dependency whose healthcheck never passes after its container started).
// `down` WITHOUT -v: containers and the network go, named volumes stay — a reinstall of an app
// removed with „keep my data" must find its data again. A failed `down` is logged and the record
// still reads not-deployed; the household's Remove clears what is left (halfStateEvidence).
if _, downErr := m.composeExecWithEnv(stackDir, env, "down"); downErr != nil {
m.logger.Printf("[ERROR] [stacks] Stack %s: removing what the failed deploy started ALSO failed: %v — containers may remain; Remove clears them", name, downErr)
} else {
m.logger.Printf("[INFO] [stacks] Stack %s: the failed deploy's containers were removed (volumes kept) — R-649", name)
}
// Revert in-memory and disk state
m.mu.Lock()
if s, ok := m.stacks[name]; ok {