fix(hub): mgmt_plane_healed alerts on the FIRST auto-heal (TASK G1) — v0.34.1

A heal marker is an event, not a baseline: construction seeds pre-existing markers
(startup false-alarm guard) but a newly-observed marker now raises the warning, so
the first auto-heal surfaces (matches the live drill). Added tests for both halves.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
2026-07-05 19:12:50 +02:00
parent 1bd054602d
commit 012e5f3ecc
4 changed files with 57 additions and 18 deletions
@@ -58,6 +58,41 @@ func TestHostMgmtPlaneChecker_NewHealAlertsOnce(t *testing.T) {
}
}
// The live-drill path: the hub started while the host was HEALTHY (no marker seeded), then the
// watchdog auto-heals and the marker appears in a later report → the FIRST observation alerts (a heal
// is an event, not a baseline). This is the case a pure trust-on-first-report would have missed.
func TestHostMgmtPlaneChecker_FirstHealAfterHealthyAlerts(t *testing.T) {
st := newMgmtStore(t)
st.SaveHostReport("h1", "c1", reportWithHeal(""), store.HostReportDenorm{}) // healthy at construction
var events []string
mc := NewHostMgmtPlaneChecker(st, func(_, et, _, _, _, _ string) { events = append(events, et) }, log.New(io.Discard, "", 0))
if len(events) != 0 || mc.GetState("h1") != "" {
t.Fatalf("healthy construction: no seed/event, got state=%q events=%v", mc.GetState("h1"), events)
}
st.SaveHostReport("h1", "c1", reportWithHeal("2026-07-05T17:07:39Z"), store.HostReportDenorm{}) // heal happens
mc.Check()
if len(events) != 1 || events[0] != "mgmt_plane_healed" {
t.Fatalf("first heal after healthy must alert once, got %v", events)
}
mc.Check() // same marker → no duplicate
if len(events) != 1 {
t.Fatalf("no duplicate on the same marker, got %v", events)
}
}
// A marker PRESENT at construction is seeded silently (avoid a false alarm on hub restart for a heal
// that predates the hub watching) — the construction-no-event half of NewHealAlertsOnce, isolated.
func TestHostMgmtPlaneChecker_PreexistingMarkerSeededSilently(t *testing.T) {
st := newMgmtStore(t)
st.SaveHostReport("h1", "c1", reportWithHeal("2026-07-05T09:00:00Z"), store.HostReportDenorm{})
var events []string
mc := NewHostMgmtPlaneChecker(st, func(_, et, _, _, _, _ string) { events = append(events, et) }, log.New(io.Discard, "", 0))
mc.Check() // same marker still latest → no alert (it was seeded)
if len(events) != 0 {
t.Fatalf("a marker present at construction must NOT alert (startup false-alarm guard), got %v", events)
}
}
func TestHostMgmtPlaneChecker_NoHealNoEvent(t *testing.T) {
st := newMgmtStore(t)
st.SaveHostReport("h1", "c1", reportWithHeal(""), store.HostReportDenorm{}) // healthy, no marker