osupdate: the health baseline is the start of the leg (inventory reading merged with the apply's own) — an app that stops during the run fails it (found live on demo-hp)
gates / gates (push) Successful in 19s

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-10-04 11:22:03 +02:00
parent 1bb8608e88
commit 9cac3462bb
2 changed files with 47 additions and 1 deletions
+31 -1
View File
@@ -240,6 +240,32 @@ func HealthVerdict(before, after *Health) (bool, string) {
return true, ""
}
// MergeBaseline is the health baseline from two readings: a container counts as running (and healthy) if EITHER
// reading saw it so. nil-safe. Pinned by TestHealth_BaselineIsTheStartOfTheLeg.
func MergeBaseline(a, b *Health) *Health {
if a == nil {
return b
}
if b == nil {
return a
}
out := &Health{DockerOK: a.DockerOK && b.DockerOK, NetworkOK: a.NetworkOK && b.NetworkOK, Controller: b.Controller,
Containers: map[string]Container{}}
for _, h := range []*Health{a, b} {
for n, c := range h.Containers {
cur, seen := out.Containers[n]
if !seen || (cur.State != "running" && c.State == "running") {
out.Containers[n] = c
continue
}
if c.State == "running" && c.Health == "healthy" {
out.Containers[n] = c
}
}
}
return out
}
// call writes the plan and runs the wrapper once.
func (l *Leg) call(ctx context.Context, runID, mode string, vmid int, rel hub.WireOSRelease, pkgs []Package) (WrapperReport, error) {
dir := l.PlanDir
@@ -374,8 +400,12 @@ func (l *Leg) Run(ctx context.Context, vmid int, trigger string) Report {
}
deadline := l.now().Add(wait)
cur := ap.HealthAfter
// The baseline is the guest as it was at the START of the leg (the inventory's reading) merged with the
// apply's own "before": an app that stops at any point during the run counts. Found live 2026-10-04: an app
// stopped between the inventory and the apply's own reading was taken as "stopped before" and ignored.
base := MergeBaseline(inv.HealthAfter, ap.HealthBefore)
for {
ok, why := HealthVerdict(ap.HealthBefore, cur)
ok, why := HealthVerdict(base, cur)
rep.Healthy, rep.HealthReason = ok, why
if ok || !l.now().Before(deadline) || ctx.Err() != nil {
break