hub v0.112.0: a floor carries a declared MinAgent past the golden (R-472)
gates / gates (push) Successful in 18s
gates / gates (push) Successful in 18s
Operator ruling 2026-09-13. Above the vouched golden, a floor saved with a declared MinAgent is served under the same agent comparison; an undeclared one is still held beyond the golden. The declaration is stored beside each floor as FLOOR=MINAGENT so it never carries to a later floor. Both floor forms require min_agent above the golden (flash floor_needs_min_agent, nothing stored). The Hosts page and the API log name the source. Vouch path and R-120 gate untouched. Scenarios A-E tested; red-proofs A and C in documentation/audits/rulings-r472-r475-2026-09-13/.
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-hub/internal/assets"
|
||||
@@ -58,6 +59,10 @@ type Handler struct {
|
||||
assetsMgr *assets.Manager
|
||||
latestVersion LatestVersionProvider
|
||||
|
||||
// floorNotes (R-472): customer → the last served-floor decision logged, so the SERVED line is written
|
||||
// once per change. Zero value is ready to use.
|
||||
floorNotes sync.Map
|
||||
|
||||
// App-email passthrough (POST /api/v1/mail). nil sender = endpoint returns 503.
|
||||
mailSender mailrelay.Sender
|
||||
mailLimiter *mailRateLimiter
|
||||
@@ -592,7 +597,20 @@ func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) {
|
||||
// on the dashboard, never silently stale.
|
||||
if fd := h.store.ResolveManagedFloor(payload.CustomerID); fd.Floor != "" {
|
||||
resp["min_controller_version"] = fd.Floor
|
||||
// R-472: say where the served floor's agent requirement came from — once per change, not per
|
||||
// report (a box reports every few minutes, and a line repeated that often is a line nobody reads).
|
||||
note := fd.Floor + "|" + fd.MinAgentSource + "|" + fd.MinAgent
|
||||
if prev, ok := h.floorNotes.Load(payload.CustomerID); !ok || prev.(string) != note {
|
||||
h.floorNotes.Store(payload.CustomerID, note)
|
||||
src := fd.MinAgentSource
|
||||
if src == "" {
|
||||
src = "none (uncoupled release)"
|
||||
}
|
||||
h.logger.Printf("[INFO] managed floor SERVED for %s: floor %s, agent requirement %q from %s (golden %s)",
|
||||
payload.CustomerID, fd.Floor, fd.MinAgent, src, fd.GoldenVersion)
|
||||
}
|
||||
} else if fd.Held {
|
||||
h.floorNotes.Delete(payload.CustomerID)
|
||||
// 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)",
|
||||
@@ -2021,8 +2039,8 @@ var allowedEventTypes = map[string]bool{
|
||||
"offsite_proof_empty": true,
|
||||
// R-431 — the hub raises this itself; allowlisted so a hub-origin event is never 400'd.
|
||||
"offsite_snapshots_dropped": true,
|
||||
"crossdrive_completed": true,
|
||||
"crossdrive_failed": true,
|
||||
"crossdrive_completed": true,
|
||||
"crossdrive_failed": true,
|
||||
// controller v0.134.1 — enlarged offsite push refused by the quota gate (warning; the controller's
|
||||
// dynamic Hungarian message is customer-grade — deliberately NO customerMessages entry, which would
|
||||
// discard the numbers (templates.go:129 priority)).
|
||||
|
||||
Reference in New Issue
Block a user