🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

feat: exclude AI Bridge usage from AI Governance seat counting - #27280

Merged
jscottmiller merged 1 commit into
mainfrom
scott/gateway-2-ai-gateway-role
Jul 28, 2026
Merged

feat: exclude AI Bridge usage from AI Governance seat counting#27280
jscottmiller merged 1 commit into
mainfrom
scott/gateway-2-ai-gateway-role

Conversation

@jscottmiller

@jscottmiller jscottmiller commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Under the new ai-gateway-seat-exclusion experiment, AI Bridge usage stops counting toward AI Governance seats.

Seat recording

Under the experiment, RecordInterception no longer records ai_seat_state usage for the initiator: AI Gateway access is licensed by the AI Governance add-on rather than per seat. This experiment is independent of workspace-capable-licensing (#27279) so the two licensing behaviors can be enabled separately. Task workspace builds still claim AI Governance seats.

Manual verification

Verified live on a dev deployment (provider chained to dev.coder.com's gateway, model gpt-5.6-luna): with the experiment off, the first bridge request from each identity type (admin, plain member, service account) wrote an ai_seat_state row (aibridge reason); with it on, requests recorded interceptions but left seat state untouched — no new rows, and existing rows' last_used_at did not advance.

Part of the gateway-accounts feature.

Stack

Part 2 of the gateway-accounts stack:

  1. feat: count only workspace-capable users toward license seats #27279: permission-based license seat counting. Behind the workspace-capable-licensing experiment and gated on the AI Governance add-on, user_limit counts only users the RBAC engine authorizes to create workspaces.
  2. This PR: stops AI Bridge usage from claiming AI Governance seats under the new ai-gateway-seat-exclusion experiment.
  3. feat: gate workspace ACL grants on matching member capability #27281: adds a use_shared capability precondition for workspace ACL grants, so workspace sharing is ineffective for (and rejected toward) users without workspace capabilities, evaluated live on every authorization. This will be done in follow-up work when we have time to look into the performance impact.

Related but independent: #27278 hides the Workspaces page create CTAs for users without workspace-create permission.

@jscottmiller
jscottmiller force-pushed the scott/gateway-2-ai-gateway-role branch 5 times, most recently from 77954f0 to 99b478e Compare July 20, 2026 15:51
@jscottmiller jscottmiller changed the title feat: add organization-ai-gateway-access role and enforce it at the AI Gateway feat: enforce AI Bridge interception permission at the AI Gateway Jul 20, 2026
@github-actions

github-actions Bot commented Jul 21, 2026

Copy link
Copy Markdown

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

Comment thread coderd/httpmw/apikey.go Outdated
Comment thread site/src/modules/roles/index.ts Outdated
"organization-workspace-creation-ban":
"Prevents this user from creating new workspaces in this organization.",
"organization-workspace-access":
"Grants the ability to create and use workspaces in this organization.",

@jscottmiller jscottmiller Jul 24, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related; plugs the missing description for the new workspace access role introduced in the minimum-implicit-member experiment.

Comment thread coderd/rbac/roles.go
@jscottmiller
jscottmiller force-pushed the scott/gateway-1-seat-counting branch from 96a4b4e to 798453d Compare July 26, 2026 17:13
@jscottmiller
jscottmiller force-pushed the scott/gateway-2-ai-gateway-role branch from d771d39 to 8fc23bf Compare July 26, 2026 19:04
@jscottmiller
jscottmiller marked this pull request as ready for review July 26, 2026 19:13
@jscottmiller
jscottmiller requested review from a team and Emyrk as code owners July 26, 2026 19:13
@coder-tasks

coder-tasks Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

No documentation changes are needed for the current diff. What remains is the ai-gateway-seat-exclusion experiment gating RecordInterception seat recording. The experiment is not in ExperimentsSafe, so default deployments are unaffected and the AI seat statement in docs/ai-coder/ai-governance.md stays accurate. docs/reference/api/schemas.md and site/src/api/typesGenerated.ts are generated and already regenerated here.

Docs will be needed when the experiment graduates: docs/ai-coder/ai-governance.md currently states a user consumes an AI seat when they use AI features such as AI Gateway.


Automated review via Coder Agents

@jscottmiller jscottmiller changed the title feat: enforce AI Bridge interception permission at the AI Gateway feat: exclude AI Bridge usage from AI Governance seat counting Jul 27, 2026

@johnstcn johnstcn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please separate out the change to API token scope to a separate PR?

@jscottmiller
jscottmiller force-pushed the scott/gateway-2-ai-gateway-role branch from 20b7455 to 9346f79 Compare July 27, 2026 19:53
@jscottmiller
jscottmiller force-pushed the scott/gateway-1-seat-counting branch from 41541c2 to 18e3599 Compare July 27, 2026 20:58
@jscottmiller
jscottmiller force-pushed the scott/gateway-2-ai-gateway-role branch from 9346f79 to d2acbf6 Compare July 27, 2026 21:03
jscottmiller added a commit that referenced this pull request Jul 28, 2026
Adds permission-based license seat counting behind the
`workspace-capable-licensing` experiment. When the experiment is enabled
and a valid license carries the AI Governance add-on, the `user_limit`
feature counts only active users the RBAC engine authorizes to create a
workspace, instead of every active user. Users without workspace-create
capability ("gateway accounts", e.g. AI-Gateway-only users) no longer
consume seats.

## How it works

- A new `GetActiveUsersAuthorizationRoles` bulk query returns effective
roles (implied member roles, org default member roles) and group
memberships for every seat-eligible user (active, not deleted, not
system, not a service account), matching `GetActiveUserCount` semantics.
- `license.CountWorkspaceCapableUsers` evaluates `workspace.create`
against the any-organization object form, which covers site-wide grants,
membership grants, and org-scoped bans in one check. Evaluation is
deduplicated on a sha256 of each user's canonical subject JSON (a fixed
sentinel user ID, sorted deduplicated roles and groups), so cost scales
with unique subjects rather than user count, and every subject field
participates in both the evaluation and the key.
- The AI Governance add-on is only known after license claims are
parsed, so `Entitlements()` passes a lazy `WorkspaceCapableUserCountFn`
(following the `ManagedAgentCountFn` precedent) and
`LicensesEntitlements` resolves it when a validated add-on is present.
Each license's `user_limit` claim becomes a candidate pair of limit and
counting mode, the most favorable pair is selected (see Behavior notes),
and the selected pair's limit, entitlement, and count become the
`user_limit` feature's terms; the warnings read the same values.
`license.Entitlements` gains `logger`, `authorizer`, and `experiments`
parameters.
- All custom roles are prefetched in a single query before evaluation
(new exported `rolestore.PrefetchCustomRoles`), and each count emits one
Info log line (capable count, eligible active users, unique subjects,
elapsed) whose presence identifies the counting mode. The count is
bounded by a 60s timeout.

## Behavior notes

- Without the experiment or without the add-on, the legacy
`GetActiveUserCount` path is unchanged.
- When the mode is active, the over-limit and expired-limit warnings say
"workspace-capable users" instead of "active users", since that is what
was counted.
- With multiple licenses, each license's `user_limit` claim forms a
candidate pair of limit and counting mode (workspace-capable for add-on
licenses, all active users otherwise), and the most favorable pair is
enforced: a pair satisfied by its own count wins over any unsatisfied
one, then higher entitlement, then higher limit. One license's limit is
never combined with another license's counting mode, so a small add-on
license can neither borrow a bigger non-add-on limit nor suppress it.
- Licenses in their grace period still gate the count; it reverts to the
legacy count only on hard expiry. While the add-on exists only on
grace-period licenses, a warning tells admins the counting mode will
revert and states the legacy active-user count they will then be
measured by.
- Count errors (database failures, timeout) abort the entitlements
computation, matching the legacy count's error semantics: the refresh
fails and the caller keeps the previous entitlements rather than a
silently different count. One exception: a stored role string that fails
to parse is logged and treated as not workspace-capable instead of
failing the refresh, since authorization fails closed on such roles
anyway.
- The experiment is deliberately not in `ExperimentsSafe`.

Part of the gateway-accounts feature; no behavior changes for
deployments without the experiment.

## Stack

Part 1 of the gateway-accounts stack. Each PR builds on the previous:

1. **#27279 (this PR)**: permission-based license seat counting. Behind
the `workspace-capable-licensing` experiment and gated on the AI
Governance add-on, `user_limit` counts only users the RBAC engine
authorizes to create workspaces.
2. **#27280**: adds the `organization-ai-gateway-access` org role
carrying the AI Bridge interception permissions (extracted from the
member floors, backfilled into org default roles by migration) and
enforces it at AI Gateway authentication; bridge usage stops claiming AI
Governance seats under the experiment.
3. ~~**#27281**: gates workspace ACL grants on matching member-level
capability (each granted action only takes effect while the recipient
holds that action in the org), so workspace sharing is ineffective for
(and rejected toward) users without workspace capabilities, evaluated
live on every authorization.~~ Tabled — excluded from the
gateway-accounts MVP.

Related but independent: **#27278** hides the Workspaces page create
CTAs for users without workspace-create permission.

## Benchmarks

`BenchmarkCountWorkspaceCapableUsers` (in `usercount_bench_test.go`, run
manually with `go test ./enterprise/coderd/license/ -bench
BenchmarkCountWorkspaceCapableUsers -benchtime 5x -run '^$'` — never
executed by CI) measures the count across user-scale and role-diversity
shapes:

| Scenario | Users | ~Unique subjects | per count |
|---|---|---|---|
| Uniform | 1k | 4 | 8.5ms |
| Uniform | 10k | 4 | 71ms |
| Uniform | 50k | 4 | 344ms |
| ManyOrgs (100 orgs) | 10k | ~200 | 112ms |
| CustomRoles (1000 org-scoped roles) | 10k | ~1000 | 168ms |
| UniquePairs (every user a distinct subject) | 10k | ~10,000 | 2.66s |

Summary:

- **Row-side cost is ~7µs per user, linear** (role parsing, subject
canonicalization, and sha256 per row). The bulk query + subject dedupe
handles 50k users in ~350ms; extrapolated 100k ≈ 0.7s. A non-issue at
the 10-minute refresh cadence.
- **Unique subjects are the dominant axis at ~0.26ms each** (role
expansion + one any-organization rego evaluation per subject). The
worst-case scenario — every user a distinct subject — costs ~2.7s at 10k
users, extrapolating to ~13s at 50k.
- **Realistic deployments sit near the cheap rows.** Subject diversity
tracks orgs × role/group combinations, not user count; only per-user
custom roles or per-user org-membership patterns approach the worst
case.
- Caveat encountered while building the harness: the roles query's plan
depends on accurate table statistics. With stale stats (e.g. right after
a bulk user import, before autovacuum ANALYZEs), the planner picks a
nested-loop plan that re-runs the aggregation per user row — a ~300×
regression (1.08s for 1k users). Fresh statistics restore the hash-join
plan; the harness ANALYZEs after seeding, so the numbers above reflect
the healthy plan.
Base automatically changed from scott/gateway-1-seat-counting to main July 28, 2026 01:44
…ew experiment

Under the ai-gateway-seat-exclusion experiment, RecordInterception no
longer records ai_seat_state usage for the initiator: AI Gateway access
is licensed by the AI Governance add-on rather than per seat. Task
workspace builds still claim AI Governance seats.
@jscottmiller
jscottmiller force-pushed the scott/gateway-2-ai-gateway-role branch from d2acbf6 to 2b14e56 Compare July 28, 2026 01:53
@jscottmiller
jscottmiller merged commit 1ab4ed8 into main Jul 28, 2026
32 checks passed
@jscottmiller
jscottmiller deleted the scott/gateway-2-ai-gateway-role branch July 28, 2026 02:05
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 28, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants