package osupdates import ( "encoding/json" "strings" "testing" ) // Operator ruling 2026-10-10 (`09` §3, `11` §5.11): "Approve kernel set" offers the NEWEST kernel version that EVERY // ring-0 box booted healthily at least once after a night step — a box may have moved on since. Before, every box's // newest step had to be the SAME kernel, and with kernels released on three days in one week (demo-felhom -22 then // -23, demo-hp -22) the button showed „the ring-0 boxes booted different kernels" and could chase a moving kernel for // ever. COMPANION RED-PROOF (observed): the pre-ruling KernelStatus fails the -22/-23 cases below. func kstep(outcome, trigger string, healthy bool, from, to string) Report { return Report{Layer: LayerKernel, Trigger: trigger, Mode: "apply", Outcome: outcome, Healthy: healthy, ReleaseID: to, Kernel: json.RawMessage(`{"from":"` + from + `","to":"` + to + `"}`)} } const k20, k22, k23 = "7.0.14-20-pve", "7.0.14-22-pve", "7.0.14-23-pve" func (f *fix) booted(t *testing.T, host, from, to string) { t.Helper() f.ingest(t, host, kstep("staged", "night", true, from, to)) f.ingest(t, host, kstep("applied", "boot", true, from, to)) } func offered(t *testing.T, f *fix) string { t.Helper() st, err := f.s.KernelStatus() if err != nil { t.Fatal(err) } if st.Waiting != "" || st.Fingerprint == "" { return "WAITING: " + st.Waiting } pj := f.s.candidatePackages(st.Fingerprint) for _, k := range []string{k20, k22, k23} { if strings.Contains(pj, "proxmox-kernel-"+k+"-signed") { return k } } return "UNKNOWN: " + pj } func TestKernelApproval_OffersNewestEveryBoxBooted(t *testing.T) { f := newFix(t) f.booted(t, "n100", k20, k22) f.booted(t, "n100", k22, k23) // moved on f.booted(t, "hp", k20, k22) if got := offered(t, f); got != k22 { t.Fatalf("n100 booted -22,-23 and hp booted -22: offered %q, want %s", got, k22) } } func TestKernelApproval_BothBootedNewerOffersNewer(t *testing.T) { f := newFix(t) f.booted(t, "n100", k20, k22) f.booted(t, "n100", k22, k23) f.booted(t, "hp", k20, k22) f.booted(t, "hp", k22, k23) if got := offered(t, f); got != k23 { t.Fatalf("both booted -23: offered %q, want %s", got, k23) } } func TestKernelApproval_ABoxThatNeverBootedOneBlocks(t *testing.T) { f := newFix(t) f.booted(t, "n100", k20, k22) got := offered(t, f) if !strings.HasPrefix(got, "WAITING: hp ") { t.Fatalf("hp never booted a new kernel: offered %q, want waiting", got) } if _, err := f.s.ApproveKernel(); err == nil { t.Fatal("approved while one ring-0 box never booted a new kernel") } } // A kernel whose NEWEST step on a box failed is not in that box's set; an older healthy kernel still is. func TestKernelApproval_AFailedLatestStepDropsThatKernelOnly(t *testing.T) { f := newFix(t) f.booted(t, "n100", k20, k22) f.booted(t, "n100", k22, k23) f.booted(t, "hp", k20, k22) f.booted(t, "hp", k22, k23) f.ingest(t, "hp", kstep("staged", "night", true, k22, k23)) f.ingest(t, "hp", kstep("fell_back", "boot", false, k22, k23)) if got := offered(t, f); got != k22 { t.Fatalf("-23 fell back on hp (its newest step): offered %q, want %s", got, k22) } } // A healthy boot without a night stage does not count (the night rule is unchanged). func TestKernelApproval_ByDayBootDoesNotCount(t *testing.T) { f := newFix(t) f.booted(t, "n100", k20, k22) f.ingest(t, "hp", kstep("staged", "signed", true, k20, k22)) f.ingest(t, "hp", kstep("applied", "boot", true, k20, k22)) if got := offered(t, f); !strings.Contains(got, "not after a night step") { t.Fatalf("offered %q, want waiting on the night step", got) } } func TestKernelKeyOrdersNumerically(t *testing.T) { if !kernelLess("7.0.14-9-pve", "7.0.14-22-pve") || !kernelLess("7.0.14-22-pve", "7.1.0-1-pve") || kernelLess(k23, k22) { t.Fatal("kernel versions must order numerically, not as text") } }