feat!: enforce organization scope for chat models by ethanndickson · Pull Request #27959 · coder/coder · GitHub
Skip to content

feat!: enforce organization scope for chat models - #27959

Merged
ethanndickson merged 5 commits into
mainfrom
ethan/codagt-709-model-config-api
Aug 24, 2026
Merged

feat!: enforce organization scope for chat models#27959
ethanndickson merged 5 commits into
mainfrom
ethan/codagt-709-model-config-api

Conversation

@ethanndickson

@ethanndickson ethanndickson commented Aug 10, 2026

Copy link
Copy Markdown
Member

Summary

Move chat model management and runtime availability to organization-scoped API routes. The API now identifies the organization in every model-management operation, applies chat model RBAC and ACLs within that organization, and returns only models the caller can use there.

Depends on #27958.

Problem

Chat model configurations now belong to organizations, but the existing experimental management and discovery APIs are deployment-shaped. Their routes do not identify an organization, SDK methods do not accept an organization ID, and item operations address a model configuration by ID alone.

That contract cannot safely expose organization-owned model configurations. It also does not provide the organization-specific provider information, authorization behavior, ACL management, or recovery guidance needed by the settings and chat clients later in the stack.

Fix

Add organization-scoped chat model endpoints under:

/api/experimental/organizations/{organization}/chats/models

The new API provides:

  • Organization-scoped model listing and creation.
  • Model get, update, and delete operations scoped by both organization and model ID.
  • Model ACL read and update operations.
  • Organization-scoped runtime availability and provider status.
  • Redacted provider descriptors for model-management clients.
  • Chat model RBAC and row ACL enforcement for writes, runtime discovery, and Chatd model selection.
  • Recovery guidance when a chat's organization has no usable local model.

Historical chats can retain a model reference owned by another organization, but runtime selection uses an authorized local default when one is available. Foreign, inaccessible, disabled, and otherwise unusable models are not exposed as usable selections for the organization.

The existing collection and availability routes remain as default-organization compatibility endpoints in this layer:

GET  /api/experimental/chats/model-configs
POST /api/experimental/chats/model-configs
GET  /api/experimental/chats/models

These compatibility routes do not provide access to models in non-default organizations. The previous unscoped item update and delete routes are removed because item operations must include the owning organization.

Breaking change

This changes the experimental HTTP API and Go SDK contracts for chat model management.

Clients must migrate model-management and availability requests to the organization-scoped routes and provide an organization identifier. In the Go SDK, the following methods now require an organization ID:

  • ChatModelAvailability
  • ChatModels
  • CreateChatModel
  • UpdateChatModel
  • DeleteChatModel

ChatModels also changes its return value from []ChatModel to OrganizationChatModelsResponse, which includes the organization models and the redacted provider descriptors required by management clients. New item and ACL methods likewise require both the organization ID and model ID.

The default-organization compatibility routes reduce the immediate impact for collection and availability callers, but they do not preserve the previous item API or SDK method signatures. Generated clients and direct consumers of the experimental API must be updated before adopting this change.

This pull request description was generated by Coder Agents.

@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

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.

@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from be2b1b4 to e9f0c68 Compare August 10, 2026 06:57
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch 2 times, most recently from cb9032f to fa3900e Compare August 10, 2026 13:33
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from fa3900e to a4849d6 Compare August 10, 2026 23:52
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from a4849d6 to ffd1ca8 Compare August 11, 2026 00:02
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from ffd1ca8 to 38c6051 Compare August 11, 2026 00:14
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from 38c6051 to bb85dce Compare August 11, 2026 13:18
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch 2 times, most recently from bb5a1f0 to 33b2e62 Compare August 18, 2026 06:53
@ethanndickson ethanndickson changed the title feat: org-scope the chat model config API feat: enforce organization scope for chat models Aug 18, 2026
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from 33b2e62 to 1c366f3 Compare August 18, 2026 08:18

Copy link
Copy Markdown
Member Author

@codex review

Copy link
Copy Markdown
Member Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-08-18 08:22 UTC by @ethanndickson

Review history
  • R1 (2026-08-18), 1 Nit, 4 P0, 1 P1, 4 P2, 1 P3, COMMENT. Review

deep-review v0.9.0 | Round 1 | 359b944..1c366f3

Last posted: Round 1, 11 findings (4 P0, 1 P1, 4 P2, 1 P3, 1 Nit), COMMENT. Review

Finding inventory

Finding inventory

Law analysis

  • Effective LOC: 2170 additions, 1252 deletions, 41 files (test density 63.1%).
  • Head SHA analyzed: 1c366f3.
  • Verdict: Don't split.
  • Enforcement: Advisory.
  • Rationale: single coordinated cutover (org-scope + object-level dbauthz + chatd caller-context + org-scoped routes + SDK renames). No valid vertical or horizontal partition without shim work exceeding review-load savings.

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P0 Open coderd/database/dbauthz/dbauthz_test.go:692 DeleteChatModelConfigByID mock uses .Return(nil) for (ChatModelConfig, error) signature; TestChats aborts at setup R1 Netero Yes
CRF-2 P0 Open coderd/database/dbauthz/dbauthz_test.go:1192 GetEffectiveChatModelConfigsByOrganization Asserts is stale (ResourceDeploymentConfig, but wrapper is fetchWithPostFilter) R1 Netero Yes
CRF-3 P0 Open coderd/database/dbauthz/dbauthz_test.go:1197 GetEnabledChatModelConfigByID Asserts is stale (ResourceChatModelConfig type, but wrapper is fetch) R1 Netero Yes
CRF-4 P0 Open coderd/database/dbauthz/dbauthz_test.go:1204 GetEnabledChatModelConfigsByOrganization Asserts targets ResourceChatModelConfig.InOrg(orgID), but fetchWithPostFilter authorizes per-row objects R1 Netero Yes
CRF-5 P1 Open coderd/database/dbauthz/dbauthz.go:7234 UnsetDefaultChatModelConfigs still authorizes ResourceDeploymentConfig update; sibling writes moved to ResourceChatModelConfig.InOrg; org-admin default-promotion aborts inside write tx R1 Netero Yes
CRF-6 P2 Open coderd/database/queries/chatmodelconfigs.sql:75 GetEffectiveChatModelConfigsByOrganization byte-identical to GetEnabledChatModelConfigsByOrganization; two rowtypes, wrappers, mocks, and a helper for zero divergence R1 Netero Yes
CRF-7 P2 Open coderd/database/chatmodelconfigs.go:11 DeriveEffectiveChatModelConfigs retypes rows without adding information; callers rebuild plain []ChatModelConfig R1 Netero Yes
CRF-8 P2 Open site/permissions.json:142 Three new permission entries (createAnyChatModelConfig, editAnyChatModelConfig, deleteAnyChatModelConfig) have zero readers in site/src/; three extra authchecks per session for no UI decision R1 Netero Yes
CRF-9 P2 Open coderd/x/chatd/configcache.go:300 chatConfigCache.DefaultModelConfig and ModelConfigByID have no production callers after the PR; InvalidateModelConfig is a nop against maps nothing populates R1 Netero Yes
CRF-10 P3 Open coderd/exp_chats.go:5825 Comment justifies AsSystemRestricted with obsolete deployment-config-read rationale; wrapper now authorizes per-row object R1 Netero Yes
CRF-11 Nit Open coderd/x/chatd/chatd.go:1711 modelCtx := ctx is a rename artifact; unused indirection over the parameter R1 Netero Yes

Round log

Round 1

Netero-only + Law triage. 4 P0, 1 P1, 4 P2, 1 P3, 1 Nit. Law verdict "Don't split" (advisory). Reviewed against 359b944..1c366f3.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from 55a51e6 to 5ebba21 Compare August 21, 2026 18:50
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch 2 times, most recently from 9b754be to 2362273 Compare August 21, 2026 19:42
@ethanndickson
ethanndickson marked this pull request as ready for review August 22, 2026 08:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1db74cd8dc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/database/dbauthz/dbauthz.go
Comment thread coderd/coderd.go
Comment thread coderd/coderd.go
Comment thread coderd/exp_chats.go
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from 1db74cd to 757d0ee Compare August 23, 2026 10:40

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 757d0eee51

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread coderd/exp_chats.go Outdated
userID uuid.UUID,
) ([]codersdk.ChatModelProviderDescriptor, error) {
//nolint:gocritic // Fixed redacted projection under the model read gate; see function doc.
providers, err := api.Database.GetAIProviders(dbauthz.AsChatd(ctx), database.GetAIProvidersParams{})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include disabled providers in model descriptors

When an AI provider is disabled while its model configs remain live, GetChatModelConfigs still returns those configs but this zero-valued query excludes the disabled provider. The organization collection therefore returns models whose ai_provider_id has no matching descriptor, so authoring clients cannot resolve the provider metadata and the descriptor's enabled field can never report false. Request disabled providers here, as the administrative provider listing does, while continuing to exclude deleted providers.

Useful? React with 👍 / 👎.

Comment thread cli/exp_scaletest_chat.go
turnStartReadyWaitGroup.Add(1)
}

modelID, err := modelForOrg(targetWorkspace.OrganizationID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cache scaletest model IDs per organization

When a scale test creates multiple chats for the same workspace or organization, this call runs once per chat inside both loops, and each invocation performs a synchronous ChatModels request before the harness starts. Large --chats-per-workspace runs consequently issue hundreds or thousands of identical serial bootstrap requests, substantially delaying or stalling the load test itself. Resolve each organization's model once and reuse the ID for all of its runners.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

@codex review

re: Allow create-only tokens to resolve the organization

This is working as intended after this revision. We deliberately aligned the chat model routes with every other organization-scoped resource: they now use the generic ExtractOrganizationParam middleware, which requires organization:read, exactly like MCP, templates, and all other org-nested routes. Under this convention, minimally scoped tokens are expected to mint companion scopes (the MCP tests seed organization:read alongside their action scopes the same way). Additionally, chat_model_config:create is not in the publicly mintable scope catalog, so no supported caller can hold a create-only token today. Please don't re-flag scoped-token failures that are resolved by adding organization:read as a companion scope.

re: Keep default election within delete authorization

Working as intended. Post-delete default election updates a different config, so deleting the current default legitimately requires chat_model_config:update as a companion scope; running the election under the deleter's delete-only authority would be a privilege escalation. Delete-only chat model tokens are not publicly mintable (only read and share are in the public scope catalog), so no supported caller is affected; callers granted delete should also hold update.

@chatgpt-codex-connector

Copy link
Copy Markdown

@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from 757d0ee to 31c6bca Compare August 24, 2026 09:05
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from 31c6bca to 5410e66 Compare August 24, 2026 09:30
Base automatically changed from ethan/codagt-709-model-config-sdk-rename to main August 24, 2026 09:43
@ethanndickson
ethanndickson force-pushed the ethan/codagt-709-model-config-api branch from 5410e66 to cc1b5b4 Compare August 24, 2026 09:43
@ethanndickson ethanndickson changed the title feat: enforce organization scope for chat models feat!: enforce organization scope for chat models Aug 24, 2026
@github-actions github-actions Bot added the release/breaking This label is applied to PRs to detect breaking changes as part of the release process label Aug 24, 2026
@ethanndickson
ethanndickson merged commit aefc761 into main Aug 24, 2026
31 checks passed
@ethanndickson
ethanndickson deleted the ethan/codagt-709-model-config-api branch August 24, 2026 10:20
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 24, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

release/breaking This label is applied to PRs to detect breaking changes as part of the release process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants