Files
felhom.eu/hub/internal/store/floor_declared.go
T
admin f181efd6a7
gates / gates (push) Successful in 18s
hub v0.112.0: a floor carries a declared MinAgent past the golden (R-472)
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/.
2026-09-13 16:47:11 +02:00

82 lines
3.8 KiB
Go

package store
import (
"strings"
"gitea.dooplex.hu/admin/felhom-hub/internal/semver"
)
// ── The MinAgent a floor DECLARES (hub v0.112.0, R-472) ───────────────────────────────────────────
//
// Publish-train rule 1 said "the manifest leads the floor": a floor above the vouched golden was HELD,
// because the only record of a controller's agent requirement was the golden manifest's MinAgent, and
// that value describes the golden, not the release being served (R-216). Operator ruling 2026-09-13
// (option B): a floor may carry its own requirement, read from the release's CHANGELOG header
// (`**MinAgent: X.Y.Z**`, enforced by the controller's gate). With it, the same agent comparison applies
// above the golden; without it, the hold is exactly as before.
//
// STORED AS "FLOOR=MINAGENT", ON PURPOSE. A declaration describes ONE release. Storing the MinAgent
// alone would let a later floor raise — made without re-declaring — silently inherit the old
// requirement, which is a stale fact wearing a current label. So a declaration counts only while the
// floor it was made for is the floor in force; any other floor reads as undeclared and is held above
// the golden. Pinned by TestDeclaredMinAgent_DoesNotCarryToADifferentFloor.
const settingGlobalFloorDeclaredMinAgent = "min_controller_version_declared_min_agent"
func encodeDeclaredMinAgent(floor, minAgent string) string {
if floor == "" || minAgent == "" {
return ""
}
return floor + "=" + minAgent
}
// decodeDeclaredMinAgent returns the declared MinAgent only if it was declared for `floor` and parses.
func decodeDeclaredMinAgent(stored, floor string) string {
parts := strings.SplitN(stored, "=", 2)
if len(parts) != 2 || floor == "" || parts[0] != floor || !semver.Valid(parts[1]) {
return ""
}
return parts[1]
}
// SetGlobalFloorDeclaredMinAgent records the MinAgent declared with the global floor (empty clears).
func (s *Store) SetGlobalFloorDeclaredMinAgent(floor, minAgent string) error {
return s.setSetting(settingGlobalFloorDeclaredMinAgent, encodeDeclaredMinAgent(floor, minAgent))
}
// GlobalFloorDeclaredMinAgent returns the MinAgent declared for the CURRENT global floor, or "".
func (s *Store) GlobalFloorDeclaredMinAgent() string {
return decodeDeclaredMinAgent(s.getSetting(settingGlobalFloorDeclaredMinAgent), s.GetGlobalMinControllerVersion())
}
// SetCustomerFloorDeclaredMinAgent records the MinAgent declared with a per-customer floor.
func (s *Store) SetCustomerFloorDeclaredMinAgent(customerID, floor, minAgent string) error {
_, err := s.db.Exec(`
UPDATE customer_configs SET min_controller_declared_min_agent = ?, updated_at = datetime('now')
WHERE customer_id = ?`,
encodeDeclaredMinAgent(floor, minAgent), customerID,
)
return err
}
// CustomerFloorDeclaredMinAgent returns the MinAgent declared for the customer's CURRENT override, or "".
func (s *Store) CustomerFloorDeclaredMinAgent(customerID string) string {
var stored, floor string
if err := s.db.QueryRow(`SELECT min_controller_declared_min_agent, min_controller_version FROM customer_configs WHERE customer_id = ?`,
customerID).Scan(&stored, &floor); err != nil {
return ""
}
return decodeDeclaredMinAgent(stored, floor)
}
// DeclaredFloorMinAgent returns the MinAgent declared with the floor that WINS for this customer —
// the per-customer override's own declaration when an override is in force, else the global floor's.
// The same precedence as EffectiveMinControllerVersion, so a declaration never attaches to a floor it
// was not made for.
func (s *Store) DeclaredFloorMinAgent(customerID string) string {
if cfg, err := s.GetCustomerConfig(customerID); err == nil && cfg != nil && cfg.MinControllerVersion != "" {
return s.CustomerFloorDeclaredMinAgent(customerID)
}
return s.GlobalFloorDeclaredMinAgent()
}