Skip to content

Commit 6419f53

Browse files
authored
fix(enterprise/coderd/license): suppress AI Governance seat-count error for not-entitled licenses (#26276)
Cherry-pick of #25885 into `release/2.34` for patch release. When a Premium deployment has no AI Governance addon but has accumulated `ai_seat_state` rows (from prior Gateway testing or Task usage), the backend emitted an error in the `LicenseBanner`. This suppresses the `EntitlementNotEntitled` case so it no longer alarms customers who never purchased the addon. Fixes https://linear.app/codercom/issue/AIGOV-392 > Generated with Coder Agents on behalf of @SasSwart
1 parent f3839eb commit 6419f53

2 files changed

Lines changed: 57 additions & 3 deletions

File tree

enterprise/coderd/license/license.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -494,9 +494,10 @@ func LicensesEntitlements(
494494
feature := entitlements.Features[codersdk.FeatureAIGovernanceUserLimit]
495495
switch {
496496
case feature.Entitlement == codersdk.EntitlementNotEntitled:
497-
// If the limit is not set
498-
entitlements.Errors = append(entitlements.Errors,
499-
fmt.Sprintf("Your deployment has %d active AI Governance seats but the license is not entitled to this feature.", actual))
497+
// Not-entitled deployments can accumulate phantom ai_seat_state
498+
// rows from prior Gateway testing or Task usage. Surfacing an
499+
// error here is alarming and inactionable for customers who
500+
// never purchased the AI Governance addon.
500501
case feature.Entitlement == codersdk.EntitlementGracePeriod && feature.Limit != nil:
501502
entitlements.Warnings = append(entitlements.Warnings,
502503
fmt.Sprintf(

enterprise/coderd/license/license_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,59 @@ func TestEntitlements(t *testing.T) {
11461146
require.NotContains(t, warning, "over the limit")
11471147
}
11481148
})
1149+
1150+
t.Run("NotEntitledSuppressed", func(t *testing.T) {
1151+
t.Parallel()
1152+
1153+
const activeSeatCount int64 = 42
1154+
1155+
ctrl := gomock.NewController(t)
1156+
mDB := dbmock.NewMockStore(ctrl)
1157+
1158+
// Premium license without the AI Governance addon.
1159+
licenseOpts := (&coderdenttest.LicenseOptions{
1160+
FeatureSet: codersdk.FeatureSetPremium,
1161+
NotBefore: dbtime.Now().Add(-time.Hour).Truncate(time.Second),
1162+
GraceAt: dbtime.Now().Add(time.Hour * 24 * 60).Truncate(time.Second),
1163+
ExpiresAt: dbtime.Now().Add(time.Hour * 24 * 90).Truncate(time.Second),
1164+
}).
1165+
UserLimit(100)
1166+
1167+
lic := database.License{
1168+
ID: 1,
1169+
JWT: coderdenttest.GenerateLicense(t, *licenseOpts),
1170+
Exp: licenseOpts.ExpiresAt,
1171+
}
1172+
1173+
mDB.EXPECT().
1174+
GetUnexpiredLicenses(gomock.Any()).
1175+
Return([]database.License{lic}, nil)
1176+
mDB.EXPECT().
1177+
GetActiveUserCount(gomock.Any(), false).
1178+
Return(int64(1), nil)
1179+
mDB.EXPECT().
1180+
GetActiveAISeatCount(gomock.Any()).
1181+
Return(activeSeatCount, nil)
1182+
mDB.EXPECT().
1183+
GetTotalUsageDCManagedAgentsV1(gomock.Any(), gomock.Any()).
1184+
Return(int64(0), nil)
1185+
mDB.EXPECT().
1186+
GetTemplatesWithFilter(gomock.Any(), gomock.Any()).
1187+
Return([]database.Template{}, nil)
1188+
1189+
entitlements, err := license.Entitlements(context.Background(), mDB, 1, 0, coderdenttest.Keys, all)
1190+
require.NoError(t, err)
1191+
require.True(t, entitlements.HasLicense)
1192+
1193+
// The not-entitled case should not produce errors about
1194+
// AI Governance seat counts.
1195+
for _, e := range entitlements.Errors {
1196+
require.NotContains(t, e, "AI Governance seats")
1197+
}
1198+
for _, w := range entitlements.Warnings {
1199+
require.NotContains(t, w, "AI Governance seats")
1200+
}
1201+
})
11491202
})
11501203
}
11511204

0 commit comments

Comments
 (0)