f181efd6a7
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/.
82 lines
3.8 KiB
Go
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()
|
|
}
|