package bootrecon import ( "testing" "gitea.dooplex.hu/admin/felhom-controller/internal/stacks" ) // R-456 — written down in 02-controller-module-map.md and pinned here: a stack with one member // REMOVED (absent, not dead) and the rest running reads as running, so the boot sweep does not repair // it; only a present-but-dead supervised member makes it degraded. Measured 2026-09-02 on demo-hp // (`docker rm -f bookstack` with bookstack-db running: not selected; both gone: repaired in 6.3 s). func TestR456_AbsentMemberIsNotABootOrphan(t *testing.T) { present := []stacks.ContainerInfo{{Name: "bookstack-db", State: "running"}} state := stacks.AggregateStateForTest(present) if state != stacks.StateRunning { t.Fatalf("one running member, one absent: state %q, want running (an absent member is not a dead one)", state) } s := stacks.Stack{Name: "bookstack", Deployed: true, State: state, Containers: present, AppConfig: &stacks.AppConfig{Deployed: true, DesiredState: stacks.DesiredStateRunning}} if isBootOrphan(s) { t.Error("a partly-dead stack must NOT be a boot orphan — repairing half a stack beside its live database is not obviously safe (R-456)") } // Control: the same member present but DEAD is degraded, and degraded IS an orphan. dead := []stacks.ContainerInfo{{Name: "bookstack-db", State: "running"}, {Name: "bookstack", State: "exited"}} if st := stacks.AggregateStateForTest(dead); st != stacks.StateDegraded { t.Fatalf("control: a present dead member must read degraded, got %q", st) } s.State, s.Containers = stacks.StateDegraded, dead if !isBootOrphan(s) { t.Error("control: a degraded stack IS a boot orphan") } }