mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(enterprise/coderd/license): remove AI Governance required banner (#28268)
## Summary The AI Governance add-on is now included in all Premium licenses, so the transitional license banner warning that AI Bridge / AI Gateway requires the AI Governance add-on is no longer accurate. This removes that warning throughout Coder. The banner text this removes: > The AI Governance add-on is required to use AI Gateway. Please reach out to your account team or sales@coder.com to learn more. ## Changes - Remove the AI Bridge soft warning block in `LicensesEntitlements`. - Remove the now-unused `hasExplicitAIBridgeEntitlement` tracking that only fed that warning. - Remove `TestAIBridgeSoftWarning` and the vestigial warning assertions/comments that pinned the removed behavior. The license banner surfaces backend entitlement warnings verbatim, so removing the warning here removes it from the dashboard banner. No frontend changes are needed; the warning string existed only in the backend. ## Testing - `go build ./enterprise/coderd/license/...` - `go vet ./enterprise/coderd/license/...` - `go test ./enterprise/coderd/license/` (license entitlement + AI Governance addon suites) --- *This PR was created by Coder Agents on behalf of @jcjiang.*
This commit is contained in:
@@ -347,12 +347,6 @@ func LicensesEntitlements(
|
||||
keys map[string]ed25519.PublicKey,
|
||||
featureArguments FeatureArguments,
|
||||
) (codersdk.Entitlements, error) {
|
||||
// TODO: Remove this tracking once AI Bridge is enforced as an add-on license.
|
||||
// Track if AI Bridge was explicitly granted via license Features (add-on)
|
||||
// vs inherited from FeatureSet (Premium). Only explicit grants should
|
||||
// suppress the soft warning for AI Bridge GA.
|
||||
hasExplicitAIBridgeEntitlement := false
|
||||
|
||||
// Each valid license's FeatureUserLimit claim forms a candidate pairing of
|
||||
// seat limit and counting mode: licenses carrying the AI Governance
|
||||
// addon count workspace-capable users, others count all active users.
|
||||
@@ -514,15 +508,6 @@ func LicensesEntitlements(
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Remove this tracking once AI Bridge is enforced as an add-on license.
|
||||
// Track explicit AI Bridge entitlement (add-on license). This is checked
|
||||
// at the license level since AI Bridge may come from the FeatureSet
|
||||
// (Premium) rather than being explicitly listed in claims.Features.
|
||||
// Only having the AI Governance addon should suppress the soft warning.
|
||||
if slices.Contains(claims.Addons, codersdk.AddonAIGovernance) {
|
||||
hasExplicitAIBridgeEntitlement = true
|
||||
}
|
||||
|
||||
// Add all features from the feature set.
|
||||
for _, featureName := range claims.FeatureSet.Features() {
|
||||
if featureName.UsesLimit() || featureName.UsesUsagePeriod() {
|
||||
@@ -924,17 +909,6 @@ func LicensesEntitlements(
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Remove this soft warning block once AI Bridge is enforced as an add-on license.
|
||||
// AI Bridge soft warning: Show warning when AI Bridge is enabled and
|
||||
// entitled via Premium FeatureSet but not via explicit add-on license.
|
||||
// This is a transitional warning as AI Bridge moves to GA and will
|
||||
// require a separate add-on license in future versions.
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
if aiBridgeFeature.Enabled && aiBridgeFeature.Entitlement.Entitled() && !hasExplicitAIBridgeEntitlement {
|
||||
entitlements.Warnings = append(entitlements.Warnings,
|
||||
"The AI Governance add-on is required to use AI Gateway. Please reach out to your account team or sales@coder.com to learn more.")
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap up by disabling all features that are not entitled.
|
||||
|
||||
@@ -57,9 +57,6 @@ func premiumRuntimeHoursFixture(t *testing.T) (*dbmock.MockStore, *coderdenttest
|
||||
// warning cannot pollute the callers' warning assertions.
|
||||
GraceAt: dbtime.Now().Add(time.Hour * 24 * 60).Truncate(time.Second),
|
||||
ExpiresAt: dbtime.Now().Add(time.Hour * 24 * 90).Truncate(time.Second),
|
||||
// The addon marks AI Bridge as explicitly entitled, suppressing
|
||||
// the unrelated "AI Governance add-on is required to use AI
|
||||
// Gateway" warning that Premium would otherwise produce.
|
||||
}).UserLimit(100).AIGovernanceAddon(100).AgentRuntimeHours(100, ptr.Ref[int64](80), ptr.Ref[int64](120))
|
||||
|
||||
lic := database.License{
|
||||
@@ -2301,163 +2298,6 @@ func TestLicenseEntitlements(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAIBridgeSoftWarning(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
aiBridgeEnabledEnablements := map[codersdk.FeatureName]bool{
|
||||
codersdk.FeatureAIBridge: true,
|
||||
}
|
||||
|
||||
aiBridgeDisabledEnablements := map[codersdk.FeatureName]bool{
|
||||
codersdk.FeatureAIBridge: false,
|
||||
}
|
||||
|
||||
aiBridgeWarningMessage := "The AI Governance add-on is required to use AI Gateway. Please reach out to your account team or sales@coder.com to learn more."
|
||||
|
||||
// A Premium license grants a managed agent limit and a grandfathered
|
||||
// agent runtime allocation by default: a nil AgentRuntimeMsFn is a hard
|
||||
// developer error and a nil ManagedAgentCountFn degrades into an
|
||||
// entitlements error, so these subtests wire zero-usage closures.
|
||||
zeroUsageArgs := license.FeatureArguments{
|
||||
ManagedAgentCountFn: func(_ context.Context, _, _ time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
},
|
||||
AgentRuntimeMsFn: func(_ context.Context, _, _ time.Time) (int64, error) {
|
||||
return 0, nil
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("NoAddon_AIBridgeOff", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// License without addon and AI Bridge disabled should NOT show warning.
|
||||
lo := (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
AccountID: "test",
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
}).Valid(time.Now())
|
||||
|
||||
generatedLicenses := []database.License{
|
||||
{
|
||||
ID: 1,
|
||||
UploadedAt: time.Now().Add(time.Hour * -1),
|
||||
JWT: lo.Generate(t),
|
||||
Exp: lo.GraceAt,
|
||||
UUID: uuid.New(),
|
||||
},
|
||||
}
|
||||
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, aiBridgeDisabledEnablements, coderdenttest.Keys, zeroUsageArgs)
|
||||
require.NoError(t, err)
|
||||
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.False(t, aiBridgeFeature.Enabled)
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
|
||||
t.Run("NoAddon_AIBridgeOn", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// License without addon and AI Bridge enabled SHOULD show warning.
|
||||
lo := (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
AccountID: "test",
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
}).Valid(time.Now())
|
||||
|
||||
generatedLicenses := []database.License{
|
||||
{
|
||||
ID: 1,
|
||||
UploadedAt: time.Now().Add(time.Hour * -1),
|
||||
JWT: lo.Generate(t),
|
||||
Exp: lo.GraceAt,
|
||||
UUID: uuid.New(),
|
||||
},
|
||||
}
|
||||
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, aiBridgeEnabledEnablements, coderdenttest.Keys, zeroUsageArgs)
|
||||
require.NoError(t, err)
|
||||
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.True(t, aiBridgeFeature.Enabled)
|
||||
assert.Equal(t, codersdk.EntitlementEntitled, aiBridgeFeature.Entitlement)
|
||||
require.Contains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
|
||||
t.Run("Addon_AIBridgeOff", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// License with addon and AI Bridge disabled should NOT show warning.
|
||||
lo := (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
AccountID: "test",
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
Addons: []codersdk.Addon{codersdk.AddonAIGovernance},
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIGovernanceUserLimit: 100,
|
||||
},
|
||||
}).Valid(time.Now())
|
||||
|
||||
generatedLicenses := []database.License{
|
||||
{
|
||||
ID: 1,
|
||||
UploadedAt: time.Now().Add(time.Hour * -1),
|
||||
JWT: lo.Generate(t),
|
||||
Exp: lo.GraceAt,
|
||||
UUID: uuid.New(),
|
||||
},
|
||||
}
|
||||
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, aiBridgeDisabledEnablements, coderdenttest.Keys, zeroUsageArgs)
|
||||
require.NoError(t, err)
|
||||
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.False(t, aiBridgeFeature.Enabled)
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
|
||||
t.Run("Addon_AIBridgeOn", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// License with addon and AI Bridge enabled should NOT show warning.
|
||||
lo := (&coderdenttest.LicenseOptions{
|
||||
AccountType: "salesforce",
|
||||
AccountID: "test",
|
||||
FeatureSet: codersdk.FeatureSetPremium,
|
||||
Addons: []codersdk.Addon{codersdk.AddonAIGovernance},
|
||||
Features: license.Features{
|
||||
codersdk.FeatureAIGovernanceUserLimit: 100,
|
||||
},
|
||||
}).Valid(time.Now())
|
||||
|
||||
generatedLicenses := []database.License{
|
||||
{
|
||||
ID: 1,
|
||||
UploadedAt: time.Now().Add(time.Hour * -1),
|
||||
JWT: lo.Generate(t),
|
||||
Exp: lo.GraceAt,
|
||||
UUID: uuid.New(),
|
||||
},
|
||||
}
|
||||
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), generatedLicenses, aiBridgeEnabledEnablements, coderdenttest.Keys, zeroUsageArgs)
|
||||
require.NoError(t, err)
|
||||
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.True(t, aiBridgeFeature.Enabled)
|
||||
assert.Equal(t, codersdk.EntitlementEntitled, aiBridgeFeature.Entitlement)
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
|
||||
t.Run("NoLicense_AIBridgeOn", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// No license with AI Bridge enabled should NOT show the soft warning
|
||||
// (it will show the generic "not entitled" warning instead).
|
||||
entitlements, err := license.LicensesEntitlements(context.Background(), time.Now(), []database.License{}, aiBridgeEnabledEnablements, coderdenttest.Keys, zeroUsageArgs)
|
||||
require.NoError(t, err)
|
||||
|
||||
aiBridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
assert.Equal(t, codersdk.EntitlementNotEntitled, aiBridgeFeature.Entitlement)
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsageLimitFeatures(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -3781,8 +3621,6 @@ func TestAIGovernanceAddon(t *testing.T) {
|
||||
// AI Bridge should be enabled without warning when addon is present.
|
||||
aibridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
|
||||
require.True(t, aibridgeFeature.Enabled, "AI Bridge should be enabled when addon is present and enablements are set")
|
||||
aiBridgeWarningMessage := "The AI Governance add-on is required to use AI Gateway. Please reach out to your account team or sales@coder.com to learn more."
|
||||
require.NotContains(t, entitlements.Warnings, aiBridgeWarningMessage, "AI Bridge warning should not appear when AI Governance addon is present")
|
||||
|
||||
// require.Equal(t, codersdk.EntitlementEntitled, aibridgeFeature.Entitlement, "AI Bridge should be entitled when addon is present")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user