hub: the held-floor REASON must match the CAUSE (CAMPAIGN-11 follow-on)
gates / gates (push) Successful in 7s

v0.97.0 introduced a second hold reason and left both surfaces printing the first. The
freshly deployed hub logged, for the campaign box:

  managed floor HELD for c11: agent "0.125.0" < MinAgent 0.113.0

which is FALSE — 0.125.0 is ABOVE 0.113.0. That box is held because its floor sits above
the vouched golden, not because of its agent. CLAUDE.md's corollary exactly: when a verdict
changes which field it counts from, the alarm text has to change with it, or a true alarm
reads as one to dismiss.

Both the ACK log line and the Hosts-dashboard HeldReason now come from one
ManagedFloorDecision.HoldReason(), and TestResolveManagedFloor_HoldReasonMatchesTheCause
pins each reason to its cause.
This commit is contained in:
2026-08-05 17:53:24 +02:00
parent cad0406e2b
commit a7f1d277b1
5 changed files with 111 additions and 5 deletions
+4 -2
View File
@@ -540,8 +540,10 @@ func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) {
if fd := h.store.ResolveManagedFloor(payload.CustomerID); fd.Floor != "" {
resp["min_controller_version"] = fd.Floor
} else if fd.Held {
h.logger.Printf("[INFO] managed floor HELD for %s: agent %q < MinAgent %s (controller floor withheld)",
payload.CustomerID, fd.AgentVersion, fd.MinAgent)
// ONE sentence, from the decision itself — the two hold reasons must never drift apart
// across this line and the dashboard (see ManagedFloorDecision.HoldReason).
h.logger.Printf("[INFO] managed floor HELD for %s: %s (controller floor withheld)",
payload.CustomerID, fd.HoldReason())
}
if h.latestVersion != nil {
if latest := h.latestVersion.LatestVersion(); latest != "" {