Canonicalize monitored-system admission enforcement

This commit is contained in:
rcourtman 2026-04-07 20:11:57 +01:00
parent e327ff9726
commit 7a47f0923e
30 changed files with 2060 additions and 201 deletions

View file

@ -154,6 +154,10 @@ type LimitStatus struct {
// Current is the observed current usage.
Current int64 `json:"current"`
// CurrentAvailable reports whether Current reflects a resolved runtime
// usage value rather than an unavailable best-effort fallback.
CurrentAvailable *bool `json:"current_available,omitempty"`
// State describes the over-limit UX state.
// Values: "ok", "warning", "enforced"
State string `json:"state"`
@ -182,10 +186,11 @@ func (c LegacyConnectionCounts) Total() int64 {
}
type EntitlementUsageSnapshot struct {
MonitoredSystems int64
Nodes int64 // legacy compatibility alias for monitored systems
Guests int64
LegacyConnections LegacyConnectionCounts
MonitoredSystems int64
MonitoredSystemsAvailable bool
Nodes int64 // legacy compatibility alias for monitored systems
Guests int64
LegacyConnections LegacyConnectionCounts
}
func (s EntitlementUsageSnapshot) monitoredSystemCount() int64 {
@ -195,6 +200,13 @@ func (s EntitlementUsageSnapshot) monitoredSystemCount() int64 {
return s.Nodes
}
func (s EntitlementUsageSnapshot) monitoredSystemCountAvailable() bool {
if s.MonitoredSystemsAvailable {
return true
}
return s.MonitoredSystems > 0 || s.Nodes > 0
}
// BuildEntitlementPayload constructs the normalized payload from LicenseStatus.
func BuildEntitlementPayload(status *LicenseStatus, subscriptionState string) EntitlementPayload {
return BuildEntitlementPayloadWithUsage(status, subscriptionState, EntitlementUsageSnapshot{}, nil)
@ -352,10 +364,11 @@ func BuildEntitlementPayloadWithUsage(
if status.MaxMonitoredSystems > 0 {
currentSystems := usage.monitoredSystemCount()
payload.Limits = append(payload.Limits, LimitStatus{
Key: MaxMonitoredSystemsLicenseGateKey,
Limit: int64(status.MaxMonitoredSystems),
Current: currentSystems,
State: LimitState(currentSystems, int64(status.MaxMonitoredSystems)),
Key: MaxMonitoredSystemsLicenseGateKey,
Limit: int64(status.MaxMonitoredSystems),
Current: currentSystems,
CurrentAvailable: boolPointer(usage.monitoredSystemCountAvailable()),
State: LimitState(currentSystems, int64(status.MaxMonitoredSystems)),
})
}
if status.MaxGuests > 0 {
@ -413,6 +426,11 @@ func remainingTrialDays(expiresAtUnix, nowUnix int64) int {
return daysRemaining
}
func boolPointer(value bool) *bool {
v := value
return &v
}
// LimitState returns the over-limit UX state string.
func LimitState(current, limit int64) string {
if limit <= 0 {

View file

@ -100,6 +100,9 @@ func TestBuildEntitlementPayloadWithUsage_CurrentValues(t *testing.T) {
if agentLimit.Current != 12 {
t.Fatalf("expected agent current 12, got %d", agentLimit.Current)
}
if agentLimit.CurrentAvailable == nil || !*agentLimit.CurrentAvailable {
t.Fatalf("expected agent current availability to be true, got %+v", agentLimit.CurrentAvailable)
}
if guestLimit.Current != 44 {
t.Fatalf("expected guest current 44, got %d", guestLimit.Current)
}
@ -145,6 +148,26 @@ func TestBuildEntitlementPayload_TrialState(t *testing.T) {
}
}
func TestBuildEntitlementPayloadWithUsage_MonitoredSystemUsageUnavailable(t *testing.T) {
status := &LicenseStatus{
Valid: true,
Tier: TierPro,
Features: append([]string(nil), TierFeatures[TierPro]...),
MaxMonitoredSystems: 50,
}
payload := BuildEntitlementPayloadWithUsage(status, "", EntitlementUsageSnapshot{}, nil)
if len(payload.Limits) != 1 {
t.Fatalf("expected one limit, got %d", len(payload.Limits))
}
if payload.Limits[0].Current != 0 {
t.Fatalf("expected unresolved current to fall back to 0, got %d", payload.Limits[0].Current)
}
if payload.Limits[0].CurrentAvailable == nil || *payload.Limits[0].CurrentAvailable {
t.Fatalf("expected unresolved current availability to be false, got %+v", payload.Limits[0].CurrentAvailable)
}
}
func TestBuildEntitlementPayload_CopiesStatusDisplayFields(t *testing.T) {
tests := []struct {
name string
@ -353,6 +376,9 @@ func TestBuildRuntimeCapabilitiesPayloadWithUsage_CurrentValues(t *testing.T) {
if payload.Limits[0].Current != 5 {
t.Fatalf("expected runtime current 5, got %d", payload.Limits[0].Current)
}
if payload.Limits[0].CurrentAvailable == nil || !*payload.Limits[0].CurrentAvailable {
t.Fatalf("expected runtime current availability true, got %+v", payload.Limits[0].CurrentAvailable)
}
}
func TestBuildRuntimeCapabilitiesPayload_NilStatusDefaultsToFreeTier(t *testing.T) {