From ff61087df2129549a43ee2f93b92fc9ff2910d4f Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Mon, 1 Jun 2026 06:29:03 +0000 Subject: [PATCH] fix(enterprise/coderd/license): suppress AI Governance seat-count error for not-entitled licenses --- enterprise/coderd/license/license.go | 7 +-- enterprise/coderd/license/license_test.go | 53 +++++++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/enterprise/coderd/license/license.go b/enterprise/coderd/license/license.go index 8c7875fa93..713637bdfc 100644 --- a/enterprise/coderd/license/license.go +++ b/enterprise/coderd/license/license.go @@ -494,9 +494,10 @@ func LicensesEntitlements( feature := entitlements.Features[codersdk.FeatureAIGovernanceUserLimit] switch { case feature.Entitlement == codersdk.EntitlementNotEntitled: - // If the limit is not set - entitlements.Errors = append(entitlements.Errors, - fmt.Sprintf("Your deployment has %d active AI Governance seats but the license is not entitled to this feature.", actual)) + // Not-entitled deployments can accumulate phantom ai_seat_state + // rows from prior Gateway testing or Task usage. Surfacing an + // error here is alarming and inactionable for customers who + // never purchased the AI Governance addon. case feature.Entitlement == codersdk.EntitlementGracePeriod && feature.Limit != nil: entitlements.Warnings = append(entitlements.Warnings, fmt.Sprintf( diff --git a/enterprise/coderd/license/license_test.go b/enterprise/coderd/license/license_test.go index 3481e5b2b1..0a19250c93 100644 --- a/enterprise/coderd/license/license_test.go +++ b/enterprise/coderd/license/license_test.go @@ -1146,6 +1146,59 @@ func TestEntitlements(t *testing.T) { require.NotContains(t, warning, "over the limit") } }) + + t.Run("NotEntitledSuppressed", func(t *testing.T) { + t.Parallel() + + const activeSeatCount int64 = 42 + + ctrl := gomock.NewController(t) + mDB := dbmock.NewMockStore(ctrl) + + // Premium license without the AI Governance addon. + licenseOpts := (&coderdenttest.LicenseOptions{ + FeatureSet: codersdk.FeatureSetPremium, + NotBefore: dbtime.Now().Add(-time.Hour).Truncate(time.Second), + GraceAt: dbtime.Now().Add(time.Hour * 24 * 60).Truncate(time.Second), + ExpiresAt: dbtime.Now().Add(time.Hour * 24 * 90).Truncate(time.Second), + }). + UserLimit(100) + + lic := database.License{ + ID: 1, + JWT: coderdenttest.GenerateLicense(t, *licenseOpts), + Exp: licenseOpts.ExpiresAt, + } + + mDB.EXPECT(). + GetUnexpiredLicenses(gomock.Any()). + Return([]database.License{lic}, nil) + mDB.EXPECT(). + GetActiveUserCount(gomock.Any(), false). + Return(int64(1), nil) + mDB.EXPECT(). + GetActiveAISeatCount(gomock.Any()). + Return(activeSeatCount, nil) + mDB.EXPECT(). + GetTotalUsageDCManagedAgentsV1(gomock.Any(), gomock.Any()). + Return(int64(0), nil) + mDB.EXPECT(). + GetTemplatesWithFilter(gomock.Any(), gomock.Any()). + Return([]database.Template{}, nil) + + entitlements, err := license.Entitlements(context.Background(), mDB, 1, 0, coderdenttest.Keys, all) + require.NoError(t, err) + require.True(t, entitlements.HasLicense) + + // The not-entitled case should not produce errors about + // AI Governance seat counts. + for _, e := range entitlements.Errors { + require.NotContains(t, e, "AI Governance seats") + } + for _, w := range entitlements.Warnings { + require.NotContains(t, w, "AI Governance seats") + } + }) }) }