mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: AI governance into AI Governance (#23553)
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -15,7 +15,7 @@ The Task details view shows the user's complete chat, workspace status and, buil
|
||||

|
||||
|
||||
> [!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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+1
-1
@@ -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();
|
||||
},
|
||||
};
|
||||
|
||||
+2
-2
@@ -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}
|
||||
|
||||
+2
-2
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user