feat: add CTAs to AI budget admin notifications (#28138)

## Problem

AI budget admin notifications tell an admin that a user crossed a budget
threshold, but give them nothing to act on: no link to the user's
budget, and no pointer to the docs.

## Changes

- Add a **Manage user AI budget** action to both admin templates,
linking to the effective group's members page filtered to the affected
user, where the per-user override is set.
- Link to the AI budget documentation from both notification bodies.
- Say when the block lifts in the limit-reached notification ("blocked
until `{{period_end}}`").
- Add an `organization_name` label, needed to build the members page
URL.
- Grant the AI Gateway daemon subject read access to organizations,
which the label lookup requires.
- Add an authorization test for the notification path, which also covers
the existing group read grant.

<details>
<summary>Screenshots</summary>

**Admin Notifications in Coder UI**

<img width="456" height="695" alt="admin_notifications"
src="https://github.com/user-attachments/assets/a579f91c-55cc-497c-87f4-235f15493725"
/>

**Admin Notification in Email**

<img width="660" height="399" alt="admin_notifications_email"
src="https://github.com/user-attachments/assets/39b876fb-f512-41e5-997f-a5269cd26ddc"
/>
</details>

Related to internal Slack threads:
* https://codercom.slack.com/archives/C096PFVBZKN/p1786051630688569
* https://codercom.slack.com/archives/C04M9CNGHGF/p1786453662306149

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by
@ssncferreira
This commit is contained in:
Susana Ferreira
2026-08-20 14:38:33 +01:00
committed by GitHub
parent 015d6a6e06
commit 363d7fc4d9
11 changed files with 235 additions and 13 deletions
+1
View File
@@ -725,6 +725,7 @@ var (
rbac.ResourceAiSeat.Type: {policy.ActionCreate}, // Required for UpsertAISeatState.
rbac.ResourceAIProvider.Type: {policy.ActionRead}, // Required to load the provider snapshot (and per-provider keys) at startup.
rbac.ResourceGroup.Type: {policy.ActionRead}, // Required to read the effective group.
rbac.ResourceOrganization.Type: {policy.ActionRead}, // Required to name the organization in budget notification links.
}),
User: []rbac.Permission{},
ByOrgID: map[string]rbac.OrgPermissions{},
@@ -0,0 +1,29 @@
UPDATE notification_templates
SET
body_template = $$User **{{.Labels.username}}** has used more than {{.Labels.threshold}}% of their {{.Labels.period}} AI budget ({{.Labels.limit}}).
Effective group: **{{.Labels.effective_group_name}}**
{{- if eq .Labels.limit_source "user_override"}}
This limit is a per-user override.
{{- end}}
AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}$$,
actions = '[]'::jsonb
WHERE
id = '2a7b0ac1-00e1-4625-9cd5-1e5933972c77';
UPDATE notification_templates
SET
body_template = $$User **{{.Labels.username}}** has reached their {{.Labels.period}} AI budget limit ({{.Labels.limit}}). Subsequent requests will be blocked.
Effective group: **{{.Labels.effective_group_name}}**
{{- if eq .Labels.limit_source "user_override"}}
This limit is a per-user override.
{{- end}}
AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}$$,
actions = '[]'::jsonb
WHERE
id = '0bafe0ea-a78b-4217-ad05-1ef12e92e025';
@@ -0,0 +1,43 @@
UPDATE notification_templates
SET
body_template = $$User **{{.Labels.username}}** has used more than {{.Labels.threshold}}% of their {{.Labels.period}} AI budget ({{.Labels.limit}}).
Effective group: **{{.Labels.effective_group_name}}**
{{- if eq .Labels.limit_source "user_override"}}
This limit is a per-user override.
{{- end}}
AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}
[How to configure AI budgets](https://coder.com/docs/ai-coder/ai-gateway/cost-controls#budget)$$,
actions = '[
{
"label": "Manage user AI budget",
"url": "{{base_url}}/organizations/{{.Labels.organization_name}}/groups/{{.Labels.effective_group_name}}?filter={{.Labels.username}}"
}
]'::jsonb
WHERE
id = '2a7b0ac1-00e1-4625-9cd5-1e5933972c77';
UPDATE notification_templates
SET
body_template = $$User **{{.Labels.username}}** has reached their {{.Labels.period}} AI budget limit ({{.Labels.limit}}). Subsequent requests will be blocked until {{.Labels.period_end}}.
Effective group: **{{.Labels.effective_group_name}}**
{{- if eq .Labels.limit_source "user_override"}}
This limit is a per-user override.
{{- end}}
AI budget period: {{.Labels.period_start}} - {{.Labels.period_end}}
Raise this user's limit to unblock them sooner. [How to configure AI budgets](https://coder.com/docs/ai-coder/ai-gateway/cost-controls#budget).$$,
actions = '[
{
"label": "Manage user AI budget",
"url": "{{base_url}}/organizations/{{.Labels.organization_name}}/groups/{{.Labels.effective_group_name}}?filter={{.Labels.username}}"
}
]'::jsonb
WHERE
id = '0bafe0ea-a78b-4217-ad05-1ef12e92e025';