fix: AI governance into AI Governance (#23553)

This commit is contained in:
Jake Howell
2026-03-25 20:06:48 +11:00
committed by GitHub
parent 98143e1b70
commit 0cea4de69e
14 changed files with 25 additions and 25 deletions
+2 -2
View File
@@ -88,7 +88,7 @@ var (
func (a Addon) Features() []FeatureName {
switch a {
case AddonAIGovernance:
// Return all AI governance features.
// Return all AI Governance features.
var features []FeatureName
for _, featureName := range FeatureNames {
if featureName.IsAIGovernanceAddon() {
@@ -306,7 +306,7 @@ func (n FeatureName) UsesUsagePeriod() bool {
}[n]
}
// IsAIGovernanceAddon returns true if the feature is an AI governance addon feature.
// IsAIGovernanceAddon returns true if the feature is an AI Governance addon feature.
func (n FeatureName) IsAIGovernanceAddon() bool {
return n == FeatureAIBridge || n == FeatureBoundary
}
+1 -1
View File
@@ -32,7 +32,7 @@ It's a good fit if you're:
- Managing AI workflows against sensitive or regulated codebases
- Expanding the use of Coder Tasks for AI-driven background work
If you already use other AI governance tools, such as third-party LLM gateways
If you already use other AI Governance tools, such as third-party LLM gateways
or vendor-managed policies, you can continue using them. Coder Workspaces can
still serve as the backend for development environments and AI workflows, with
or without the AI Governance Add-On.
+1 -1
View File
@@ -15,7 +15,7 @@ The Task details view shows the user's complete chat, workspace status and, buil
![VS Code IDE Extension Details View](../images/guides/ai-agents/vs_code_tasks_extension_details.png)
> [!NOTE]
> Both Community and Premium deployments include 1,000 Agent Workspace Builds for proof-of-concept use. Community deployments do not have access to [AI Bridge](./ai-bridge/index.md) or [Agent Boundaries](./agent-boundaries/index.md). To scale beyond the 1,000 build limit or enable AI governance features, the [AI Governance Add-On](./ai-governance.md) provides expanded usage pools that grow with your user count. [Contact us](https://coder.com/contact) to discuss pricing.
> Both Community and Premium deployments include 1,000 Agent Workspace Builds for proof-of-concept use. Community deployments do not have access to [AI Bridge](./ai-bridge/index.md) or [Agent Boundaries](./agent-boundaries/index.md). To scale beyond the 1,000 build limit or enable AI Governance features, the [AI Governance Add-On](./ai-governance.md) provides expanded usage pools that grow with your user count. [Contact us](https://coder.com/contact) to discuss pricing.
## Supported Agents (and Models)
+1 -1
View File
@@ -3,7 +3,7 @@
The [AI Governance Add-On](./ai-governance.md) requires reporting usage data to Tallyman, a Coder-managed server for billing and reporting purposes. Coder only captures and sends the following information, related to your deployment ID:
- number of agent workspace builds consumed
- number of AI governance seats consumed
- number of AI Governance seats consumed
No user-identifiable information or additional metrics are sent to Tallyman. This information is also shared with [Metronome](https://metronome.com), a Stripe product and Coder partner for usage-based and reporting.
+3 -3
View File
@@ -495,15 +495,15 @@ func LicensesEntitlements(
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.", featureArguments.ActiveAISeatCount))
fmt.Sprintf("Your deployment has %d active AI Governance seats but the license is not entitled to this feature.", featureArguments.ActiveAISeatCount))
case feature.Entitlement == codersdk.EntitlementGracePeriod && feature.Limit != nil:
entitlements.Warnings = append(entitlements.Warnings,
fmt.Sprintf(
"Your deployment has %d active AI governance seats but the license with the limit %d is expired.",
"Your deployment has %d active AI Governance seats but the license with the limit %d is expired.",
featureArguments.ActiveAISeatCount, *feature.Limit))
case feature.Limit != nil && featureArguments.ActiveAISeatCount > *feature.Limit:
entitlements.Warnings = append(entitlements.Warnings, fmt.Sprintf(
"Your deployment has %d active AI governance seats but is only licensed for %d.",
"Your deployment has %d active AI Governance seats but is only licensed for %d.",
featureArguments.ActiveAISeatCount, *feature.Limit))
}
}
+6 -6
View File
@@ -952,7 +952,7 @@ func TestEntitlements(t *testing.T) {
require.EqualValues(t, 100, *aiGovernanceSeatLimit.Limit)
require.Len(t, entitlements.Warnings, 1)
require.Equal(t, "Your deployment has 127 active AI governance seats but is only licensed for 100.", entitlements.Warnings[0])
require.Equal(t, "Your deployment has 127 active AI Governance seats but is only licensed for 100.", entitlements.Warnings[0])
})
}
@@ -1897,7 +1897,7 @@ func TestAIGovernanceAddon(t *testing.T) {
empty := map[codersdk.FeatureName]bool{}
t.Run("AIGovernanceAddon enables AI governance features when enablements are set", func(t *testing.T) {
t.Run("AIGovernanceAddon enables AI Governance features when enablements are set", func(t *testing.T) {
t.Parallel()
db, _ := dbtestutil.NewDB(t)
db.InsertLicense(context.Background(), database.InsertLicenseParams{
@@ -1912,7 +1912,7 @@ func TestAIGovernanceAddon(t *testing.T) {
Exp: dbtime.Now().Add(time.Hour),
})
// Enable AI governance features in enablements.
// Enable AI Governance features in enablements.
enablements := map[codersdk.FeatureName]bool{
codersdk.FeatureAIBridge: true,
codersdk.FeatureBoundary: true,
@@ -1935,7 +1935,7 @@ func TestAIGovernanceAddon(t *testing.T) {
// require.Equal(t, codersdk.EntitlementEntitled, boundaryFeature.Entitlement, "Boundary should be entitled when addon is present")
})
t.Run("AIGovernanceAddon not present disables AI governance features", func(t *testing.T) {
t.Run("AIGovernanceAddon not present disables AI Governance features", func(t *testing.T) {
t.Parallel()
db, _ := dbtestutil.NewDB(t)
db.InsertLicense(context.Background(), database.InsertLicenseParams{
@@ -1992,7 +1992,7 @@ func TestAIGovernanceAddon(t *testing.T) {
require.True(t, entitlements.HasLicense)
// TODO: Readd this test once AI Bridge is enforced as an add-on license.
// AI governance features should be enabled but in grace period.
// AI Governance features should be enabled but in grace period.
// aibridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
// require.True(t, aibridgeFeature.Enabled, "AI Bridge should be enabled during grace period")
// require.Equal(t, codersdk.EntitlementGracePeriod, aibridgeFeature.Entitlement, "AI Bridge should be in grace period")
@@ -2060,7 +2060,7 @@ func TestAIGovernanceAddon(t *testing.T) {
require.Equal(t, "Feature AI Governance User Limit must be set when using the AI Governance addon.", entitlements.Errors[0])
// TODO: Readd this test once AI Bridge is enforced as an add-on license.
// AI governance features should not be entitled when validation fails.
// AI Governance features should not be entitled when validation fails.
// aibridgeFeature := entitlements.Features[codersdk.FeatureAIBridge]
// require.False(t, aibridgeFeature.Enabled, "AI Bridge should not be enabled when addon validation fails")
// require.Equal(t, codersdk.EntitlementNotEntitled, aibridgeFeature.Entitlement, "AI Bridge should not be entitled when addon validation fails")
@@ -6,7 +6,7 @@ const meta: Meta<typeof AIGovernanceAddOnCard> = {
"pages/DeploymentSettingsPage/LicensesSettingsPage/AIGovernanceAddOnCard",
component: AIGovernanceAddOnCard,
args: {
title: "AI governance",
title: "AI Governance",
unit: "Seats",
actual: 750,
limit: 1000,
@@ -41,7 +41,7 @@ export const AIGovernanceAddOnCard: FC<AIGovernanceAddOnCardProps> = ({
<TooltipTrigger asChild>
<button
type="button"
aria-label="AI governance seat information"
aria-label="AI Governance seat information"
className="m-0 inline-flex appearance-none border-0 bg-transparent p-0 text-content-secondary"
>
<InfoIcon className="size-3" />
@@ -76,7 +76,7 @@ function aiGovernanceLimitFromLicenses(
}
/**
* Resolves the displayed AI governance user limit for summary charts.
* Resolves the displayed AI Governance user limit for summary charts.
*
* When the feature is not entitled yet, JWT claims can still carry the
* purchased add-on seat limit while entitlements may report limit 0.
@@ -120,7 +120,7 @@ export const UsageBarFromLicenseClaims: Story = {
const canvas = within(canvasElement);
await expect(canvas.getByText("750")).toBeInTheDocument();
await expect(
canvas.getByRole("heading", { name: "AI governance add-on usage" }),
canvas.getByRole("heading", { name: "AI Governance add-on usage" }),
).toBeInTheDocument();
},
};
@@ -34,7 +34,7 @@ export const AIGovernanceUsersConsumption: FC<
<div className="flex items-center justify-center rounded-lg border border-solid p-4">
<div className="flex flex-col items-center justify-center">
<div className="flex flex-col items-center justify-center">
<span className="text-base">AI governance add-on usage</span>
<span className="text-base">AI Governance add-on usage</span>
<span className="text-content-secondary text-center max-w-[464px] mt-2">
AI Governance is not included in your current license. Contact{" "}
<Link href="mailto:sales@coder.com">sales</Link> to upgrade your
@@ -48,7 +48,7 @@ export const AIGovernanceUsersConsumption: FC<
return (
<SeatUsageBarCard
title="AI governance add-on usage"
title="AI Governance add-on usage"
actual={aiGovernanceUserFeature?.actual}
limit={effectiveLimit}
/>
@@ -124,7 +124,7 @@ export const PremiumWithoutAIGovernanceAddOn: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
await expect(canvas.queryByText("Add-ons")).not.toBeInTheDocument();
await expect(canvas.queryByText("AI governance")).not.toBeInTheDocument();
await expect(canvas.queryByText("AI Governance")).not.toBeInTheDocument();
},
};
@@ -71,7 +71,7 @@ export const LicenseCard: FC<LicenseCardProps> = ({
license,
aiGovernanceUserFeature,
);
// A license "wins" when its AI governance limit matches the merged limit.
// A license "wins" when its AI Governance limit matches the merged limit.
const isWinningAiGovernanceLicense =
aiGovernanceMergedLimit !== undefined &&
aiGovernanceLimit > 0 &&
@@ -227,7 +227,7 @@ export const LicenseCard: FC<LicenseCardProps> = ({
</div>
<div className="mt-3 flex flex-wrap gap-3">
<AIGovernanceAddOnCard
title="AI governance"
title="AI Governance"
unit="Seats"
actual={aiGovernanceDisplayActual}
limit={aiGovernanceLimit}
@@ -43,7 +43,7 @@ export const Empty: Story = {
},
};
/** Premium + AI governance usage bars; AI governance shows `SeatUsageBarCard` (not the not-entitled placeholder). */
/** Premium + AI Governance usage bars; AI Governance shows `SeatUsageBarCard` (not the not-entitled placeholder). */
export const ActiveAIGovernanceAddOnUsage: Story = {
args: {
userLimitActual: 1923,
@@ -67,7 +67,7 @@ export const ActiveAIGovernanceAddOnUsage: Story = {
await expect(canvas.getByText("1,923")).toBeInTheDocument();
await expect(canvas.getByText("2,500")).toBeInTheDocument();
await expect(
canvas.getByRole("heading", { name: "AI governance add-on usage" }),
canvas.getByRole("heading", { name: "AI Governance add-on usage" }),
).toBeInTheDocument();
await expect(canvas.getByText("512")).toBeInTheDocument();
await expect(canvas.getByText("1,000")).toBeInTheDocument();