You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phases 129-130: admin model allow-lists and BYOK (#118)
* Implement Phases 129-130: admin-gated model allow-lists and BYOK
Add gitsema admin models list|allow|deny|reset for superadmin control
over which embedding profiles and narrator/guide configs are usable,
server-wide or per-org (Phase 129). Add request-scoped BYOK credentials
to narrate/explain/guide across CLI, HTTP, and MCP that bypass the
allow-list entirely and are never persisted (Phase 130).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MDWd8BkGMSwZiMeDBVfvk8
* Simplify: dedupe BYOK CLI parsing and admin allow/deny policy logic
Extract parseByokCliOpts/addByokOptions into src/cli/lib/byok.ts, shared by
narrate.ts/explain and guide.ts (was triplicated). Collapse modelPolicy.ts's
near-duplicate allowServer/denyServer/allowOrg/denyOrg into shared
applyAllow/applyDeny helpers, preserving all existing exported signatures.
Logs two lower-value findings (pinned-profile exemption placement, BYOK
body-shape normalization) to feature-ideas.md per the simplify-pass policy.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Add superadmin-controlled model allow-lists and bring-your-own-key (BYOK) support. Operators can now restrict which embedding profiles or narrator/guide model configs are usable, server-wide or per-org, via `gitsema admin models list|allow|deny|reset --kind <embedding|narrator|guide> [--org <name>]`. Independently, `narrate`/`explain`/`guide` (CLI, HTTP, and MCP) accept request-scoped BYOK credentials (`--byok-http-url`/`--byok-api-key`/`--byok-model`/`--byok-max-tokens`/`--byok-temperature` and equivalent HTTP/MCP fields) that bypass the allow-list entirely and are never persisted.
|`gitsema auth sso list <username>`| List SSO identities linked to a user (operator-only) |
132
132
|`gitsema audit log [--org <org>] [--repo <repo-id>] [--limit <n>]`| Query the identity/authorization audit trail (grant/token/login/org-membership/repo-move events), newest first (operator-only) |
133
+
|`gitsema admin models list --kind <embedding\|narrator\|guide> [--org <name>]`| Show the effective allowed set for a model kind, server-wide or for an org (Phase 129, operator-only) |
134
+
|`gitsema admin models allow\|deny <identifier> --kind <kind> [--org <name>]`| Enable/disable a defined embedding profile or narrator/guide model config, server-wide or narrowed for an org (never widening past the server-wide set; operator-only) |
135
+
|`gitsema admin models reset --kind <kind> [--org <name>]`| Revert a model kind's policy to default-allow-all, server-wide or for an org (operator-only) |
133
136
134
137
#### `gitsema index start [options]`
135
138
@@ -340,8 +343,13 @@ Both commands are **safe-by-default**: with no narrator model configured (or wit
340
343
|`--model <name>`| — | Narrator model name to use (overrides active selection) |
341
344
|`--narrate`| off | Call the configured LLM narrator and return prose (default: return evidence only) |
342
345
|`--evidence-only`| on | Return raw commit evidence without calling the LLM (this is the default) |
346
+
|`--byok-http-url <url>`| — | Request-scoped narrator LLM endpoint (bring-your-own-key, Phase 130); bypasses configured/allow-listed models and the DB entirely, never persisted |
347
+
|`--byok-api-key <key>`| — | Bearer token for `--byok-http-url`|
348
+
|`--byok-model <name>`| — | Model id sent to `--byok-http-url` (defaults to the endpoint default) |
349
+
|`--byok-max-tokens <n>`| — | Max tokens per BYOK call |
350
+
|`--byok-temperature <n>`| — | Temperature for BYOK calls |
343
351
344
-
Configure a narrator model with `gitsema models add <name> --narrator --http-url <url> [--key <token>] --activate`, with a local Ollama model (`gitsema models add <name> --narrator --provider ollama [--global-name <tag>] --activate`, see "Ollama" below), or with a local CLI AI tool: `gitsema models add <name> --narrator --provider cli --cli-command <tool> [--cli-args "<args>"] --activate` (see "CLI-based AI tool backends" below).
352
+
Configure a narrator model with `gitsema models add <name> --narrator --http-url <url> [--key <token>] --activate`, with a local Ollama model (`gitsema models add <name> --narrator --provider ollama [--global-name <tag>] --activate`, see "Ollama" below), or with a local CLI AI tool: `gitsema models add <name> --narrator --provider cli --cli-command <tool> [--cli-args "<args>"] --activate` (see "CLI-based AI tool backends" below). Alternatively, pass `--byok-http-url` (and optionally `--byok-api-key`/`--byok-model`) for a one-off, never-persisted request-scoped model — it bypasses the configured/allow-listed model selection entirely (Phase 130).
345
353
346
354
#### `--narrate` on other commands
347
355
@@ -378,8 +386,13 @@ All outbound content (prompts, tool results) is passed through the same secret/P
378
386
|`--model <name>`| Guide/narrator model name to use |
379
387
|`--no-context`| Skip gathering git context (faster but less accurate) |
380
388
|`-i, --interactive`| Start an interactive REPL session (one question per line, multi-turn — the agent session is reused across turns) |
389
+
|`--byok-http-url <url>`| Request-scoped guide LLM endpoint (bring-your-own-key, Phase 130); bypasses configured/allow-listed models and the DB entirely, never persisted |
390
+
|`--byok-api-key <key>`| Bearer token for `--byok-http-url`|
391
+
|`--byok-model <name>`| Model id sent to `--byok-http-url` (defaults to the endpoint default) |
392
+
|`--byok-max-tokens <n>`| Max tokens per BYOK call |
393
+
|`--byok-temperature <n>`| Temperature for BYOK calls |
381
394
382
-
Configure a guide model with `gitsema models add <name> --guide --http-url <url> [--key <token>] --activate`, or with a local CLI AI tool: `gitsema models add <name> --guide --provider cli --cli-command <tool> [--use-mcp] --activate` (see "CLI-based AI tool backends" below).
395
+
Configure a guide model with `gitsema models add <name> --guide --http-url <url> [--key <token>] --activate`, or with a local CLI AI tool: `gitsema models add <name> --guide --provider cli --cli-command <tool> [--use-mcp] --activate` (see "CLI-based AI tool backends" below). Alternatively, pass `--byok-http-url` (and optionally `--byok-api-key`/`--byok-model`) for a one-off, never-persisted request-scoped model (Phase 130).
383
396
384
397
**Ollama:**`--provider ollama` configures `narrate`/`explain`/`guide` against a local Ollama
385
398
server with no API key. It defaults `--http-url` to `http://localhost:11434` (no trailing
Copy file name to clipboardExpand all lines: docs/PLAN.md
+13-3Lines changed: 13 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4924,16 +4924,26 @@ Deviations from the design doc, discovered during implementation:
4924
4924
| Phase | Spec section | Title | Deliverable |
4925
4925
|---|---|---|---|
4926
4926
|**128**| §5 Phase 1 | Multi-profile embedding serving | Server config shape for N named embedding profiles; `gitsema tools serve` builds a `Map<profileName, {textProvider, codeProvider}>` instead of one global pair; `repos.profileName` column pinned at first index and immutable after; CLI `gitsema index start --profile <name>`, `gitsema repos info` surfacing the pinned profile. ✅ complete |
4927
-
|**129**| §5 Phase 2 | Admin-gated enabled sets | Superadmin admin CLI/route enabling/disabling defined profiles and narrator/guide configs server-wide; org-level narrowing (never widening) reusing the Multi-Tenant Auth Track's org/role model; picker UX (pre-selected/disabled when exactly one profile is allowed, real picker otherwise). |
4928
-
|**130**| §5 Phase 3 | BYOK for narrator/guide | Request-scoped credential field on `narrate`/`explain`/`guide` CLI/HTTP/MCP entry points; one-off provider construction via `createNarratorProviderFor()` with no persistence path; "lock to none" support (empty allow-list as a valid, tested state). |
4927
+
|**129**| §5 Phase 2 | Admin-gated enabled sets | Superadmin admin CLI/route enabling/disabling defined profiles and narrator/guide configs server-wide; org-level narrowing (never widening) reusing the Multi-Tenant Auth Track's org/role model; picker UX (pre-selected/disabled when exactly one profile is allowed, real picker otherwise). ✅ complete |
4928
+
|**130**| §5 Phase 3 | BYOK for narrator/guide | Request-scoped credential field on `narrate`/`explain`/`guide` CLI/HTTP/MCP entry points; one-off provider construction via `createNarratorProviderFor()` with no persistence path; "lock to none" support (empty allow-list as a valid, tested state). ✅ complete |
4929
4929
4930
-
**Status:**Phase 128 complete *(completed vX.Y.Z)*; Phases 129–130 not started.
- The `--profile <name>` flag landed on `gitsema remote-index` (not `gitsema index start`) — `remote-index` is the command backed by the persistent repo registry and first-index profile-pinning concept the design doc describes; a local-only `index start` has no registry row to pin a profile to.
4934
4934
- Query-time profile-aware embedding (search/evolution routes using the *pinned* profile's text provider instead of the process-wide default) is out of scope for this pass — search still embeds queries with the server's default text provider regardless of which profile a result's source repo was indexed with.
4935
4935
- Testing "a profile disabled but the repo keeps working" is deferred to Phase 129, since the enable/disable admin mechanism doesn't exist yet.
4936
4936
4937
+
**Phase 129 deviations from spec:**
4938
+
- Policy storage reuses the existing `settings` key-value table (JSON blobs keyed `model_allowlist:server:<kind>` / `model_allowlist:org:<orgId>:<kind>`) instead of a new schema table — no migration, schema stays at v32.
4939
+
- Admin surface is CLI-only (`gitsema admin models list|allow|deny|reset --kind <embedding|narrator|guide> [--org <name>]`); no HTTP/MCP route was added in this pass (no `docs/parity.md` entry to add since the command has no other-interface counterpart to compare against — see parity note below).
4940
+
- Org-level narrowing is fully wired for `embedding` profiles (the `gitsema tools serve` HTTP route has org context via `getRepoOrgId`), but **not** for `narrator`/`guide` configs — `gitsema models activate` enforces the server-wide allow-list only, since there is no HTTP entry point for narrator/guide activation to attach org context to. Org narrowing for narrator/guide remains a CLI-side-only allow-list (server-wide) until such a route exists.
4941
+
- The Phase 128-deferred regression case — a repo already pinned to an embedding profile keeps working even after that profile is later `denyServer`'d — is implemented as an explicit guard in `src/server/routes/remote.ts`'s profile-resolution block (the new 403 check is skipped whenever `pinnedProfileName` is set) and covered by a dedicated test.
4942
+
4943
+
**Phase 130 deviations from spec:**
4944
+
- BYOK credentials are always resolved to `provider: 'chattydeer'` (HTTP backend) — a CLI-subprocess-backed BYOK variant was judged out of scope; HTTP/OpenAI-compatible is the realistic BYOK case the design doc anticipates.
4945
+
- Implemented as an opt-in `byok` field threaded through the *existing*`resolveNarratorProvider`/`resolveGuideConfig`/`resolveGuideProvider` functions (short-circuits before any DB access or allow-list check) rather than a separate code path, so every existing call site (CLI `narrate`/`explain`/`guide`, HTTP `/api/v1/narrate`/`/explain`/`/guide/chat`, MCP `narrate_repo`/`explain_issue_or_error`) gained BYOK support by construction. CLI flags: `--byok-http-url`/`--byok-api-key`/`--byok-model`/`--byok-max-tokens`/`--byok-temperature`; HTTP body: nested `byok: {httpUrl, apiKey, model, maxTokens, temperature}` (narrator routes) or snake_case `byok: {http_url, api_key, model, max_tokens, temperature}` (guide route, matching its existing snake_case body convention); MCP: flattened `byok_http_url`/`byok_api_key`/`byok_model`/`byok_max_tokens`/`byok_temperature` fields.
4946
+
4937
4947
---
4938
4948
4939
4949
### Phase 131 — `gitsema index doctor --fix` auto-repair *(completed)*
Copy file name to clipboardExpand all lines: docs/parity.md
+4-1Lines changed: 4 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -2,7 +2,7 @@
2
2
3
3
This document tracks the availability of gitsema tools and commands across all interfaces, and the implementation of common flags across the CLI. It serves as the single source of truth for interface parity and helps identify gaps, inconsistencies, and opportunities for unification.
4
4
5
-
**Last updated:** 2026-06-23 (the only date in this document — see §4 for why)
5
+
**Last updated:** 2026-06-24 (the only date in this document — see §4 for why)
6
6
**Maintainer note:** Any tool change, interface change, or flag addition must be reflected in the tables below and in the canonical sections of `CLAUDE.md` / `docs/features.md` / `README.md`.
7
7
8
8
---
@@ -139,6 +139,7 @@ This table shows which tools/commands are available in which interface. A checkm
@@ -487,6 +488,8 @@ If you find a discrepancy, **update this file first**, then propagate the change
487
488
| Phase 117 | MCP Streamable HTTP transport |`tools mcp --http` — MCP's standard network transport, supersedes `--websocket` for MCP clients; no change to tool availability (§0) |
488
489
| Phase 119 (review10 close-out) | Network-transport resource bounds | Payload/connection/session caps and non-loopback-without-key warnings added to all `--websocket`/`--http`/`--tcp` modes; no parity impact (security hardening, internal) |
489
490
| Phase 120 |`tools lsp --tcp` deprecated | No tool/method availability change — `--tcp` still works identically, now prints a deprecation notice on every invocation steering callers to `--websocket --key` (§0); not yet scheduled for removal |
491
+
| Phase 129 | Admin-gated enabled sets | New `gitsema admin models list\|allow\|deny\|reset` CLI command (operator-only, no other interface — see Tool Matrix); no flag changes to existing tools |
492
+
| Phase 130 | BYOK for narrator/guide |`--byok-http-url`/`--byok-api-key`/`--byok-model`/`--byok-max-tokens`/`--byok-temperature` flags added to `narrate`/`explain`/`guide` (CLI), nested `byok` body field on the matching HTTP routes, flattened `byok_*` fields on the matching MCP tools; no Tool Matrix changes (additive flags on already-listed tools) |
490
493
| Future | CLI Interactive | Full CLI with autocomplete, history, interactive UI |
491
494
| Future | Web UI | Browser-based dashboard with visualization |
0 commit comments