hub v0.97.0 — the floor stops being served past the agent it depends on (CAMPAIGN-11)
gates / gates (push) Successful in 8s
gates / gates (push) Successful in 8s
R-216, the hub half. ResolveManagedFloor's own comment says it exists to "never push a controller past the agent it depends on", and it compared against ArtifactManifest.MinAgent — which by ITS own comment describes the GOLDEN's controller. publish-train-rules.md rule 3 states the rule about the FLOOR's controller. Measured live: golden 0.192.0 / MinAgent 0.113.0, floor 0.200.0, agent 0.120.0 — served, and the box was pushed onto a controller needing agent 0.125.0. A floor ABOVE the vouched golden is now HELD with its own reason (HeldBeyondGolden), reusing Part D's dashboard visibility. Nobody types a number twice: the vouched MinAgent keeps its meaning, the guard stops applying it to versions it does not describe. An uncoupled release is untouched; an unparseable golden degrades rather than gating. R-222: the report ACK's escrow object gains superseded_present / superseded_at, counting only rows that actually carry an identity blob. One boolean and one timestamp, for one message. No read path — that link is still unbuilt. Red-proof: removing the floor-above-golden branch reproduces the campaign's measurement.
This commit is contained in:
@@ -93,3 +93,90 @@ func TestResolveManagedFloor(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// ── R-216: the floor may not be served ABOVE the version the manifest describes ─────────────────
|
||||
//
|
||||
// `publish-train-rules.md` rule 3 is about the FLOOR's controller; the implementation compared
|
||||
// against the GOLDEN's MinAgent. Those are the same number only while the floor sits at or below the
|
||||
// golden. Measured live 2026-08-05 (CAMPAIGN-11 Phase 1): golden 0.192.0 / MinAgent 0.113.0, a
|
||||
// per-customer floor of 0.200.0, a box on agent 0.120.0 — 0.120.0 ≥ 0.113.0, so the floor was served
|
||||
// and the box was pushed onto a controller needing agent 0.125.0. Its customer was then told their
|
||||
// correct recovery code was wrong.
|
||||
//
|
||||
// RED-PROOF: delete the `semver.Compare(d.Floor, d.GoldenVersion) > 0` branch from
|
||||
// ResolveManagedFloor and TestResolveManagedFloor_R216_FloorAboveGolden FAILS — the box is served
|
||||
// past its agent again, reproducing the campaign's measurement exactly. Demonstrated failing before
|
||||
// these tests were kept.
|
||||
func TestResolveManagedFloor_R216_FloorAboveGolden(t *testing.T) {
|
||||
t.Run("the campaign's exact numbers → HELD, not served", func(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
_ = s.SetGlobalMinControllerVersion("0.200.0")
|
||||
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.192.0", MinAgent: "0.113.0"})
|
||||
setHostAgent(t, s, "c11", "c11-36d660", "0.120.0")
|
||||
|
||||
fd := s.ResolveManagedFloor("c11")
|
||||
if !fd.Held || fd.Floor != "" {
|
||||
t.Fatalf("R-216 RETURNED: a floor ABOVE the vouched golden was served, so the hub pushed a controller whose agent requirement it does not know: %+v", fd)
|
||||
}
|
||||
if !fd.HeldBeyondGolden {
|
||||
t.Errorf("the hold must be distinguishable from the ordinary below-MinAgent hold (they need different operator text): %+v", fd)
|
||||
}
|
||||
if fd.GoldenVersion != "0.192.0" {
|
||||
t.Errorf("the decision must carry the golden it compared against: %+v", fd)
|
||||
}
|
||||
})
|
||||
|
||||
// The hold is about the FLOOR being unknown, not about the agent — so it holds even for a box
|
||||
// whose agent comfortably exceeds the manifest's MinAgent. That is the whole point: the hub cannot
|
||||
// know what the served version needs.
|
||||
t.Run("held even when the agent clears the manifest's MinAgent", func(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
_ = s.SetGlobalMinControllerVersion("0.200.0")
|
||||
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.192.0", MinAgent: "0.113.0"})
|
||||
setHostAgent(t, s, "c1", "h1", "0.125.0")
|
||||
|
||||
if fd := s.ResolveManagedFloor("c1"); !fd.Held {
|
||||
t.Fatalf("a floor above the golden must hold regardless of the agent: %+v", fd)
|
||||
}
|
||||
})
|
||||
|
||||
// SCENARIO C — an uncoupled release is untouched. A fleet must not be frozen by a comparison that
|
||||
// now over-fires: with no MinAgent vouched there is no gating at all, above the golden or not.
|
||||
t.Run("uncoupled release above the golden → still served (Scenario C)", func(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
_ = s.SetGlobalMinControllerVersion("0.200.0")
|
||||
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.192.0", MinAgent: ""})
|
||||
setHostAgent(t, s, "c1", "h1", "0.70.0")
|
||||
|
||||
fd := s.ResolveManagedFloor("c1")
|
||||
if fd.Held || fd.Floor != "0.200.0" {
|
||||
t.Fatalf("an uncoupled release must be served exactly as before: %+v", fd)
|
||||
}
|
||||
})
|
||||
|
||||
// The ordinary arrangement — floor at or below the golden — is completely unchanged.
|
||||
t.Run("floor at the golden → unchanged behaviour", func(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
_ = s.SetGlobalMinControllerVersion("0.192.0")
|
||||
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "0.192.0", MinAgent: "0.113.0"})
|
||||
setHostAgent(t, s, "c1", "h1", "0.120.0")
|
||||
|
||||
fd := s.ResolveManagedFloor("c1")
|
||||
if fd.Held || fd.Floor != "0.192.0" {
|
||||
t.Fatalf("floor == golden with a satisfied MinAgent must serve: %+v", fd)
|
||||
}
|
||||
})
|
||||
|
||||
// An unreadable golden must not gate the fleet — degrade to the pre-existing behaviour.
|
||||
t.Run("unparseable golden → falls through to the agent comparison", func(t *testing.T) {
|
||||
s := newTestStore(t)
|
||||
_ = s.SetGlobalMinControllerVersion("0.200.0")
|
||||
_ = s.SetArtifactManifest(ArtifactManifest{GoldenVersion: "", MinAgent: "0.113.0"})
|
||||
setHostAgent(t, s, "c1", "h1", "0.120.0")
|
||||
|
||||
fd := s.ResolveManagedFloor("c1")
|
||||
if fd.Held || fd.Floor != "0.200.0" {
|
||||
t.Fatalf("an unknown golden must not freeze the fleet: %+v", fd)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user