feat(organizations): Enterprise groups with composable model-access policies - #4891
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryThe incremental revision adds Verified in this revision
Files Reviewed (3 files)
Scope: PR-only incremental diff Previous Review Summaries (5 snapshots, latest commit b7bd102)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit b7bd102)Status: No Issues Found | Recommendation: Merge Executive SummaryThe incremental revision removes the unenforced MCP server access policy end-to-end and fixes the authorized-non-member 500 in the organization defaults endpoint; both changes verified against the current code with no new findings. Verified in this revision
Files Reviewed (15 files)
Scope: PR-only incremental diff Previous review (commit dd2deab)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe defaults endpoint now evaluates the policy against the already-authorized organization row (good), but the member subject it pairs with still turns Kilo-admin and parent-org inherited access into an uncaught Overview
Issue Details (click to expand)WARNING
Verified as fixed in this revision
Files Reviewed (3 files)
Scope: incremental diff Fix these issues in Kilo Cloud Previous review (commit 9c57da6)Status: 1 Issue Found | Recommendation: Safe to merge after optional cleanup Executive SummaryThis revision fixes all four previously-flagged issues it targets (BYOK pinning, Slack default model, prefetch rejection handling, inert type assertions); the only remaining remark is a Vercel BYOK test fixture that asserts a provider/model pairing production can never produce. Overview
Issue Details (click to expand)SUGGESTION
Verified as fixed in this revision
Files Reviewed (7 files)
Scope: incremental diff Fix these issues in Kilo Cloud Previous review (commit 6fc2efc)Status: 9 Issues Found | Recommendation: Address before merge Executive SummaryThe rebase onto Overview
Issue Details (click to expand)WARNING
SUGGESTION
Verified as fixed in this revision
Files Reviewed (incremental diff since `7d8535a4`, rebase-normalized)
Assumptions: the branch was rebased onto Fix these issues in Kilo Cloud Previous review (commit 7d8535a)Status: 6 Issues Found | Recommendation: Address before merge Executive SummaryIncremental review of Overview
Issue Details (click to expand)WARNING
SUGGESTION
Previous findings re-verified as fixed
Files Reviewed (32 files in the incremental diff)
Fix these issues in Kilo Cloud Additional previous summary content was truncated to keep this comment within platform limits. Reviewed by kimi-k3 · Input: 67.5K · Output: 12.4K · Cached: 756.7K Review guidance: REVIEW.md from base branch |
Add Enterprise-only member groups that carry composable, strictly discriminated policies, plus an organization-level set of default policies applied to every direct member. - model access policy (all, none, or selected models and providers) enforced at the gateway and reflected in catalogs, defaults, and integrations - MCP server access policy for granting organization MCP Gateway servers; configuration only until gateway enforcement lands - absence of an applicable policy preserves existing organization-wide access; only an explicit none mode grants nothing - organization model and provider restrictions remain a hard ceiling that group grants cannot exceed - DrawerStack-based management UI backed by exhaustive client and server policy registries, so a new policy type must declare every entry
- keep BYOK pinning when `provider.ignore` covers every provider the caller holds keys for; an empty BYOK map sent `only: []` with no credential, so the request billed Kilo's Vercel account while still skipping the zero-balance rejection as a BYOK request - restore pre-policy parity in the Slack/Discord/Linear default-model helper: unrestricted organizations get `globalDefault` again instead of a possibly virtual `settings.default_model` - mark the gateway group-policy prefetch handled so an early return or a bypassed request cannot turn a policy-context failure into an unhandled rejection - make the schema/DB compatibility assertions real build errors via `AssertTrue`; the previous conditional types could resolve to `never`, which satisfies `extends true` and never failed typecheck
The policy type picker is where an owner commits to adding the policy, so the caveat that MCP Gateway still resolves connections from per-member assignments belongs there rather than only in a footnote inside the editor.
…rganization `getOrganizationGroupPolicyContext` re-read the organization row, so the defaults endpoint evaluated model access against a second read instead of the organization it had already loaded and authorized. Callers holding the row now pass it in, which also removes a duplicate read of the same row. This restores the endpoint's deny-list fallback and its 409 "all models blocked" response, both covered by the existing route tests.
…xture `userByok` only contains providers that serve the requested model, so pairing an OpenAI key with a Claude request pinned an unroutable expectation. Anthropic plus Bedrock is the reachable partial-ignore case.
The groups product contract and session handoff now live in the local docs directory, so the `.plans` allowlist entries for them are no longer needed.
That product is being retired, so the policy type is removed from the DB shape, the runtime schemas, both registries, the editor UI, and the policy editor data query. Model access is again the only policy type. The type picker gains a non-interactive "More policies coming soon" row so it still reads as a list of choices once model access is configured.
…non-members Kilo admins and parent-organization owners are authorized for an organization without holding a membership row, so the member policy subject rejected them with an UNAUTHORIZED TRPCError that escaped this route as a 500. They belong to no group, so they now resolve against organization-level policy via an explicit `allowNonMember` subject flag. Enforcement paths leave the flag unset and still fail closed for callers without direct access.
…ders `ensureOrganizationAccess` grants Kilo admins and parent-organization owners access without a membership row, so the member policy subject rejected them from `listAvailableModels` and the model-preferences allow-list. Both are read paths behind an authorization check, so they now pass `allowNonMember` and resolve organization-level policy, matching pre-groups behavior. The gateway and token-authenticated catalog routes keep the strict subject. Adds a parity suite covering organizations that predate groups: legacy model/provider restrictions in `organizations.settings` with no group and no `organization_group_policy_settings` row must produce exactly the decisions the pre-groups predicate produced, and reading policy must not materialize a row.
Summary
Adds Enterprise-only, organization-local member groups that compose strict, discriminated policies. Groups are flat and many-to-many with direct members. Model access is the first policy type, built so that adding another requires an explicit entry in each layer's exhaustive registry.
organization_groups,organization_group_memberships, andorganization_group_policy_settingstables with a single generated Drizzle migration (0203_organization_groups.sql).organizations.groupstRPC router gated on Enterprise plan and role (owner manages; billing manager read-only; members see only their own group names).DrawerStack-based management — group details with an inline policy collection, an add-policy type picker, and per-policy editors. The model-access editor reuses the existing model/provider selector with group-specific grant semantics.Effective access
A member's access is
organization ceiling ∩ (default policies + the union of their group grants).Absence of configuration is never a restriction: when no default or group policy applies, the member keeps existing organization-wide access, and only an explicit
nonemode (or an emptyselectedgrant) yields no access. Organization model deny lists and provider allow-lists remain a hard ceiling that group grants cannot exceed.Policy architecture
Persistence stays generic —
@kilocode/db/schema-typesowns only thejsonbcolumn shapes and holds no runtime policy logic. Everything else is split by runtime boundary:apps/web/src/lib/organizations/group-policies/*.server.tsplusregistry.server.tscomponents/organizations/groups/policies/plusregistry.client.tsExhaustive registries keyed by
OrganizationGroupPolicyTypeforce a new variant to be implemented in every layer, and each schema asserts structural compatibility with its persisted DB shape throughAssertTrue, so drift fails typecheck rather than passing silently.UX notes
Layers).p-5, eyebrow section labels); clickable rows usecursor-pointer.Deploy note
defaultFreeModelonGET /api/organizations/:id/defaultsis nowstring | null. It returnsnullonly when the organization's policy denies every free model, rather than falling back todefaultModel, which may be paid. Client handling ofnullshould be confirmed inKilo-Org/kilocodebefore this ships.Rebase note
This branch has been rebased onto
maintwice. #4846 (owner-configurable Efficient model pools) made a policy subject required ongetAvailableModelsForOrganization, soauto-routing-pool-validation.tspasses{ type: 'defaultAccess' }: pool membership is organization-level configuration, and a member subject would additionally require direct membership that a parent-organization owner or platform admin may not have. The second rebase also renumbered this branch's migration to0203.Testing
organization-groups,effective-model-access.server, thedefaultsroute, the OpenRouter gateway route, the Vercel provider (including new BYOK-pinning regressions), and auto-routing pool validation.pnpm --filter web typecheck,pnpm --filter @kilocode/db typecheck, web + db lint,pnpm format:check, andgit diff --check— passing.pnpm drizzle generatereports no pending schema changes against the committed migration.Notes