Skip to content

feat: authorize org chat model configs#27355

Draft
mafredri wants to merge 1 commit into
mathias/codagt-709-databasefrom
mathias/codagt-709-rbac
Draft

feat: authorize org chat model configs#27355
mafredri wants to merge 1 commit into
mathias/codagt-709-databasefrom
mathias/codagt-709-rbac

Conversation

@mafredri

@mafredri mafredri commented Jul 20, 2026

Copy link
Copy Markdown
Member

Review boundary

Review slice 2 of CODAGT-709. Do not merge this PR separately. The full dependent stack will be reviewed, squashed into one commit, reverified, and merged atomically to main.

This slice owns ResourceChatModelConfig, direct user and group read ACLs, RegoSQL filtering, role permissions, and database authorization. It deliberately has no ActionUse.

Depends on: #27354
Next: #27356

Verification

  • RBAC and role tests
  • RegoSQL compile tests
  • dbauthz method coverage
  • combined backend head: 28,050 tests passed, 57 skipped
  • make gen, make fmt, make lint, make build, and git diff --check
Complete CODAGT-709 stack plan

CODAGT-709: review-only hard-cut stack for organization-scoped chat model configs

Direction for approval

Implement CODAGT-709 as one atomic hard cut, decomposed into stacked PRs only to make review manageable. The PRs are not independently deployable or mergeable to main. Each PR depends on the complete behavior introduced by later layers. After all layers are reviewed and the stack head passes verification, squash the full stack into one commit and merge it atomically to main.

The atomic change adds organization ownership, clones and remaps existing data, activates RBAC and ACLs, replaces global APIs and settings with organization-qualified contracts, scopes chat runtime and caches, migrates every supported caller, and removes the legacy contracts. There is no compatibility period and no intermediate production state.

CODAGT-714 remains separate. CODAGT-709 supplies the organization-scoped model CRUD, settings, and ACL APIs that CODAGT-714 will consume, but does not implement the organization picker, model-management UI migration, settings UI migration, empty-state UI, or sharing UI.

Observable end state

  • chat_model_configs is organization-owned and organization_id is non-null.
  • Every legacy config, including soft-deleted history, is cloned once for every non-deleted organization with a fresh UUID.
  • Chats, messages, queued messages, and debug runs reference the clone belonging to their chat organization.
  • Each organization has at most one non-deleted default. A disabled model may remain default.
  • Model-bearing site settings and user settings are organization-qualified and reference organization model IDs.
  • ResourceChatModelConfig supports create, read, update, delete, and share. There is no ActionUse.
  • Direct user and group ACL grants provide read only.
  • Migrated configs grant the organization's Everyone group read and have an empty user ACL.
  • Organization-qualified discovery, CRUD, ACL, settings, SDK, frontend query, runtime, cache, CLI, scaletest, and telemetry behavior is active.
  • Global model and model-bearing settings routes, SDK methods, query helpers, storage keys, and runtime fallbacks are removed.
  • Existing chats continue using their remapped model clone. ACL access is not re-evaluated on every turn.
  • CODAGT-714 UI is not included.

Settled decisions

Stack and merge model

  • PR boundaries exist only for review.
  • PR 1 is based on main; every later PR is based on the previous PR branch.
  • No individual stack layer is merged to main.
  • No layer is required to support deployment, rollback, or mixed-version operation by itself.
  • The final stack head must compile, pass all tests, and implement the complete hard cut.
  • After review, squash the complete stack and merge the squash commit atomically to main.
  • Review comments are amended in the layer that owns the affected behavior, then all descendants are rebased.

Migration

The up migration performs the original hard cut in one transaction:

  1. Add nullable organization_id, user_acl, and group_acl to chat_model_configs.
  2. Drop the deployment-wide default index.
  3. Build a temporary mapping from every legacy config and every non-deleted organization to a fresh model-config UUID.
  4. Clone every legacy config, including soft-deleted historical configs, to every non-deleted organization.
  5. Preserve all model fields, provider references, creator and updater values, timestamps, deleted state, enabled state, options, context limit, compression threshold, and default state.
  6. Seed each clone with an empty user ACL and read for the organization's Everyone group, keyed by organization ID.
  7. Remap chats.last_model_config_id through the chat's organization.
  8. Add organization_id to chat_messages, backfill it from the parent chat, and remap model_config_id through that organization.
  9. Add organization_id to chat_queued_messages, backfill it from the parent chat, and remap model_config_id through that organization.
  10. Remap chat_debug_runs.model_config_id through the debug run's chat organization.
  11. Clone model-bearing site settings once per non-deleted organization and replace valid legacy model IDs with the matching clone ID.
  12. Clone user personal model overrides and compaction thresholds only for active organization memberships and replace valid legacy model IDs with the matching clone ID.
  13. Preserve malformed or historically unmappable setting values according to the original migration behavior rather than inventing a replacement.
  14. Delete legacy global site and user setting keys.
  15. Delete legacy global model rows.
  16. Make model, message, and queued-message organization columns non-null.
  17. Add unique (id, organization_id) keys for model configs and chats.
  18. Replace model and chat foreign keys with same-organization composite foreign keys for chats, messages, and queued messages.
  19. Add an organization index on model configs.
  20. Add a partial unique index on organization_id where is_default = true AND deleted = false.

No compatibility, coexistence, inheritance metadata, synchronization, fallback, or shadow rows are added.

Downgrade

The down migration preserves the original deterministic collapse behavior:

  • Keep all organization model IDs and historical references while removing organization ownership from the schema.
  • Select the default organization as the restore source. If it is unavailable, select the earliest non-deleted organization by creation time and ID.
  • Restore global model overrides and advisor configuration from the restore-source organization.
  • Restore one personal override per user and context, preferring the default organization and then the earliest active membership that has the setting.
  • Restore one compaction threshold per user and model key with the same preference order.
  • Clear active defaults outside the restore-source organization so the restored global schema has one non-deleted default.
  • Restore the original non-organization foreign keys.
  • Drop child organization columns, model organization and ACL columns, composite keys, organization indexes, and organization default uniqueness.
  • Recreate the singleton deployment-wide default index.

The downgrade is intentionally lossy with respect to organization-specific settings and defaults, but it preserves model rows and references.

RBAC and ACLs

  • Add ResourceChatModelConfig as an organization-scoped resource.
  • Supported actions are create, read, update, delete, and share.
  • Do not add ActionUse.
  • Implement ChatModelConfig.RBACObject() with organization, identifier, user ACL, and group ACL.
  • Add ChatModelConfigConverter for RegoSQL.
  • Add authorized list queries for enabled and administrative model-config views.
  • Replace ResourceDeploymentConfig checks on model CRUD with the new resource.
  • Organization-qualified list and discovery use ActionRead authorization filtering.
  • Item GET requires read. Item PATCH requires update. Item DELETE requires delete. ACL GET and PATCH require share.
  • Item and ACL authorization failures return 404 to conceal existence.
  • Direct user and group ACL roles map only to read. Reject other ACL actions.
  • Organization Everyone-group read grants make migrated configs available to organization members.
  • Template RBAC entitlement behavior follows the existing ACL-backed resource pattern and is covered by enterprise tests.
  • Organization administrators and applicable site roles receive management permissions through the existing role definitions.
  • Existing chats do not add per-turn ACL revocation. New selections and user-configured overrides require read access when selected.

Defaults and writes

  • Model create and list are organization-qualified.
  • Create inserts the requested organization ID, empty user ACL, and Everyone-group read ACL.
  • If an organization has no non-deleted default, its first created model becomes default.
  • Setting a model as default clears the prior default only in the same organization.
  • Updating or deleting a model repairs the default only in the same organization.
  • Each organization has at most one non-deleted default.
  • A disabled model may remain default.
  • Provider deletion soft-deletes dependent model configs across organizations through an explicitly authorized database path.
  • Model IDs supplied to organization settings or chat creation must belong to the same organization.
  • Cross-organization identifiers are rejected without exposing the foreign resource.

API

Replace the legacy model routes with the hard-cut organization contract:

  • GET /api/experimental/organizations/{organization}/chats/models
  • GET /api/experimental/organizations/{organization}/chat-model-configs
  • POST /api/experimental/organizations/{organization}/chat-model-configs
  • GET /api/experimental/chat-model-configs/{modelConfig}
  • PATCH /api/experimental/chat-model-configs/{modelConfig}
  • DELETE /api/experimental/chat-model-configs/{modelConfig}
  • GET /api/experimental/chat-model-configs/{modelConfig}/acl
  • PATCH /api/experimental/chat-model-configs/{modelConfig}/acl

Replace model-bearing settings routes with organization-qualified routes under:

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

The scoped settings are:

  • General, explore, title-generation, and compaction model overrides.
  • Advisor configuration.
  • User personal model overrides.
  • User compaction thresholds.

The deployment-wide personal-model-overrides enablement flag remains deployment-scoped because it is not model-bearing.

All legacy global model discovery, model CRUD, ACL, and model-bearing settings routes are removed. Do not retain gone handlers, redirects, default-organization adapters, or fallback routes.

Settings storage

  • Site-config model override keys are prefixed with organization ID.
  • Advisor configuration keys are prefixed with organization ID.
  • Personal override keys include organization ID before context.
  • Compaction threshold keys include organization ID and model-config ID.
  • All setting reads and writes require a concrete organization.
  • Runtime does not fall back to global keys.
  • API validation requires selected model IDs to belong to the requested organization.
  • Malformed stored settings retain the original handler and runtime error behavior established by the complete implementation.

Chat model selection and runtime

  • Chat creation requires organization_id and resolves every model choice in that organization.
  • An explicit model_config_id must identify an enabled, readable config in the requested organization.
  • If no explicit model is supplied, resolve the user's organization-scoped personal override when enabled, otherwise resolve the organization's default.
  • Existing-chat send, edit, retry, queued-message, title generation, compaction, advisor, subagent, and quick-generation paths take organization from the persisted chat.
  • All model lookups include organization.
  • Messages, queued messages, and debug runs persist the same organization as their chat and a model ID from that organization.
  • Same-organization database constraints provide the final enforcement boundary.
  • Runtime does not consult a deployment default organization or global model setting.
  • Existing chats continue with the model clone selected by migration. No per-turn ACL revocation is introduced.

Cache and pubsub

  • Model-config cache keys include organization ID and model-config ID.
  • Default-model cache keys use organization ID.
  • Advisor cache keys use organization ID.
  • Singleflight keys include organization ID.
  • Model and advisor pubsub events include organization ID.
  • Model mutation invalidates the model and default entries for the affected organization.
  • Advisor mutation invalidates only the affected organization.
  • Provider topology invalidation may clear model state globally because AI providers remain deployment-scoped.
  • Generation counters reject stale in-flight fills after invalidation.

SDK, frontend, CLI, scaletest, and telemetry

  • Replace legacy Go SDK methods with organization-qualified methods. Go methods use distinct signatures or names as required; no legacy method remains at the stack head.
  • TypeScript API methods require organization for discovery and model-bearing settings.
  • React Query keys include organization for every organization-owned model and setting.
  • Existing chat detail uses chat.organization_id.
  • New-chat model discovery uses the existing selectedOrg in AgentCreateForm.
  • New-chat personal override reads also use the selected organization after the scoped settings API exists.
  • CLI and scaletest callers supply organization and create or select a config per organization.
  • Telemetry reports organization-owned model configs without collapsing them into deployment-global resources.
  • Generated OpenAPI, SDK types, RBAC resources, mocks, database code, and docs reflect only the final hard-cut contract.

CODAGT-714 exclusion

CODAGT-709 does not implement:

  • A new organization picker.
  • Model or MCP settings page picker integration.
  • Model-management page migration.
  • Model sharing UI.
  • Empty-organization UI.
  • CODAGT-714 Storybook coverage.

CODAGT-714 consumes the final organization CRUD, ACL, and settings APIs after CODAGT-709 merges atomically.

Ruled-out directions

  • Independently deployable expansion PRs.
  • Compatibility or coexistence periods.
  • Global and organization rows existing as supported runtime resources at the same deployed revision.
  • Inheritance metadata or one-way synchronization.
  • Shadow tables or shadow rows.
  • Global settings fallback.
  • Legacy server routes, gone handlers, redirects, or aliases.
  • Default-organization compatibility adapters.
  • Aggregating organization rows through a global route.
  • Folding CODAGT-714 UI into CODAGT-709.
  • Splitting the final reviewed stack across multiple merges to main.

Review-only PR stack

The active stack begins at #27354. Closed PRs #27337, #27338, and #27340 are not stack layers and provide no merge dependency.

PR 1, #27354: database hard cut

Branch: mathias/codagt-709-database

Base: main.

Review focus: Schema, migration correctness, organization-qualified storage, same-organization constraints, defaults, and generated database interfaces.

Boundary:

  • Complete up and down migrations and migration fixtures.
  • Clone and remap model configs, chats, messages, queued messages, debug runs, site settings, and user settings.
  • Organization and ACL fields on model configs.
  • Organization fields on messages and queued messages.
  • Same-organization keys and foreign keys.
  • Per-organization default index.
  • Organization-qualified model and model-bearing settings queries.
  • ACL queries and authorized-query source hooks required by the RBAC layer.
  • Generated database, mock, metrics, constraint, dump, and test artifacts.
  • Database, migration, default, downgrade, and cross-organization constraint tests.

PR 2, #27355: RBAC and dbauthz

Branch: mathias/codagt-709-rbac

Base: #27354.

Review focus: Resource policy, ACL-backed authorization, authorized SQL filtering, role behavior, and database authorization coverage.

Boundary:

  • ResourceChatModelConfig with create, read, update, delete, and share.
  • ChatModelConfig.RBACObject() and generated RBAC objects and SDK resources.
  • RegoSQL converter and authorized list compilation.
  • dbauthz methods for every changed or added model-config query.
  • Role and Template RBAC entitlement behavior.
  • User and group ACL read-only semantics.
  • Authorization, role, RegoSQL, dbauthz, and concealment prerequisites and tests.

PR 3, #27356: API, SDK, and organization settings

Branch: mathias/codagt-709-api

Base: #27355.

Review focus: Hard-cut HTTP contracts, SDK contracts, model CRUD, ACL validation, defaults, organization-scoped settings, and item concealment.

Boundary:

  • Organization-qualified model catalog, list, and create handlers.
  • Item GET, PATCH, DELETE and ACL GET, PATCH.
  • Organization-qualified model overrides, advisor configuration, personal overrides, and compaction thresholds.
  • Removal of global model and model-bearing settings server routes.
  • Final Go SDK methods and tests with no legacy methods retained at the stack head.
  • Model validation, default repair, provider deletion integration, ACL validation, and 404 concealment.
  • API, SDK, enterprise RBAC, settings, malformed-value, default, ACL, and cross-organization tests.
  • Generated API references and documentation for the hard-cut contract.

PR 4, #27357: runtime and backend callers

Branch: mathias/codagt-709-runtime

Base: #27356.

Review focus: End-to-end backend organization isolation and migration of every non-site caller.

Boundary:

  • Organization-aware model resolution for chat creation and all existing-chat runtime paths.
  • Organization-scoped defaults, personal overrides, model overrides, advisor, and compaction thresholds.
  • Model, default, and advisor cache keying, invalidation, generation, and singleflight isolation.
  • Organization-scoped pubsub events.
  • Chat message, queued-message, and debug-run organization persistence.
  • CLI, scaletest, telemetry, test helpers, and other backend caller migrations.
  • Chatd architecture update.
  • Chatd, cache, concurrency, CLI, scaletest, telemetry, and backend integration tests.
  • Removal of remaining backend global-query callers and runtime fallbacks.

Pending site slice: frontend callers and final stack-head verification

Base: #27357.

Status: Preserved but not opened as an active PR. It is blocked until separately owned CODAGT-714 supplies organization context to all seven settings and model-management pages listed below. Repository hooks typecheck the complete site, so the site slice cannot pass required hooks while those pages still call the hard-cut organization-required API without an organization.

The seven blocking pages are:

  1. site/src/pages/AISettingsPage/CoderAgentsPage/CoderAgentsPage.tsx
  2. site/src/pages/AISettingsPage/ModelsPage/AddModelPage/AddModelPage.tsx
  3. site/src/pages/AISettingsPage/ModelsPage/ModelsPage.tsx
  4. site/src/pages/AISettingsPage/ModelsPage/UpdateModelPage/UpdateModelPage.tsx
  5. site/src/pages/AgentsPage/AgentSettingsAPIKeysPage.tsx
  6. site/src/pages/AgentsPage/AgentSettingsCompactionPage.tsx
  7. site/src/pages/AgentsPage/AgentSettingsUserAgentsPage.tsx

CODAGT-714 owns adding organization context, picker behavior, model-management UI migration, settings UI migration, sharing UI, empty-organization UI, and its Storybook coverage. CODAGT-709 does not implement those behaviors.

After CODAGT-714 supplies organization context, the pending site slice contains only CODAGT-709 caller migration and verification work:

  • TypeScript API methods and React Query helpers for the final organization-qualified contracts.
  • Organization-keyed query and invalidation keys.
  • Existing chat detail model and setting reads scoped by chat.organization_id.
  • New-chat catalog, model config, and personal override reads scoped by AgentCreateForm.selectedOrg.id.
  • Migration of remaining non-CODAGT-714 site callers to required organization arguments.
  • Removal of legacy frontend API methods, query helpers, and unscoped cache keys.
  • Storybook and frontend tests for chat detail and new-chat organization isolation.
  • Repository-wide no-legacy-caller checks and full stack-head verification.

Stack dependencies and atomic merge procedure

  1. feat(coderd/database): scope chat models to orgs #27354 targets main for review context.
  2. feat: authorize org chat model configs #27355 targets feat(coderd/database): scope chat models to orgs #27354's branch.
  3. feat: expose org chat model APIs #27356 targets feat: authorize org chat model configs #27355's branch.
  4. feat: scope chat model runtime to orgs #27357 targets feat: expose org chat model APIs #27356's branch.
  5. The pending site slice is based on feat: scope chat model runtime to orgs #27357 and remains blocked until CODAGT-714 supplies organization context to the seven pages above.
  6. Review each active PR against its immediate base so reviewers see only that boundary.
  7. Apply review fixes in the owning PR and rebase every descendant.
  8. Do not merge feat(coderd/database): scope chat models to orgs #27354, feat: authorize org chat model configs #27355, feat: expose org chat model APIs #27356, feat: scope chat model runtime to orgs #27357, or the site slice individually to main.
  9. After the site slice is unblocked, run the complete verification suite on the full stack head and confirm its diff against main contains the complete hard cut.
  10. Squash the full main...stack-head diff into one commit on a final integration branch.
  11. Re-run required hooks and verification on the exact squash commit.
  12. Merge that single squash commit atomically to main.
  13. Close the review-only stacked PRs as superseded by the atomic squash merge, preserving their review history.

Full-stack invariants

  • The stack head contains no global model row, global model-bearing setting, legacy route, legacy SDK method, global query helper, or fallback runtime path.
  • Every model lookup and model-bearing setting lookup has a concrete organization.
  • Every chat, message, queued message, and debug-run model reference belongs to the same organization as its chat.
  • Direct ACL grants provide read only.
  • Unauthorized item access is concealed with 404.
  • Each organization has at most one non-deleted default.
  • Existing chats use the remapped clone for their organization.
  • No per-turn ACL revocation is added.
  • CODAGT-714 UI is absent.
  • The squash commit is the only CODAGT-709 commit merged to main.

Verification at the stack head and squash commit

Run all checks on both the reviewed PR 7 head and the exact final squash commit:

  • Migration fixture and full up and down migration execution.
  • Clone count and fresh-ID assertions for every legacy-config and active-organization pair, including soft-deleted history.
  • Correct remap of chats, messages, queued messages, debug runs, site settings, and user settings.
  • Same-organization foreign-key enforcement and cross-organization rejection.
  • Per-organization defaults, default repair, and disabled-default behavior.
  • User ACL, group ACL, Everyone group, share authority, non-read action rejection, Template RBAC, and 404 concealment.
  • Organization model CRUD, discovery, settings, and SDK tests.
  • Chat creation explicit/default/personal selection and all existing-chat runtime model paths.
  • Advisor, title, explore, compaction, and subagent organization isolation.
  • Cache key, invalidation, generation, and singleflight concurrency tests with unique organization IDs and no timing sleeps.
  • CLI, scaletest, telemetry, frontend query, Storybook, and integration coverage.
  • Repository-wide search proving no legacy route, method, global settings key usage, unscoped model query, or default-organization fallback remains.
  • make gen.
  • make fmt.
  • make lint.
  • Relevant Go and frontend test targets.
  • Storybook tests and build for changed stories.
  • Production site build.
  • make build or the repository's required final build entry point.
  • Mandatory pre-commit and pre-push hooks.
  • git diff --check.

A failure in any layer is a failure of the complete stack. Do not merge a partial layer or add compatibility behavior to make an intermediate PR independently deployable.

References

🤖 This PR was created with the help of Coder Agents, and will be reviewed by a human. 🏂🏻

@linear-code

linear-code Bot commented Jul 20, 2026

Copy link
Copy Markdown

CODAGT-709

Use organization ownership and direct read ACLs for model discovery and management without introducing a separate use permission.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant