merge: integrate v7-rbac-visibility on top of skills feature

Brings together the two unmerged feature lines onto one integration branch:
- skills/review/proposals/revisions (via fix/mcpd-instance-health-and-retry,
  which contains the full skills-1..7 chain + mcpd env/retry/readiness fix)
- v7 visibility scope + ownership for Llms and Agents

Auto-merge resolved schema.prisma, mcpd main.ts, mcplocal config, CLI create,
and completions with no conflicts. Both Prisma migrations coexist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michal
2026-06-16 21:31:35 +01:00
24 changed files with 828 additions and 45 deletions

View File

@@ -431,10 +431,100 @@ mid-task reverts the row to pending instead of failing the caller.
See [inference-tasks.md](./inference-tasks.md) for the full data
model, async API, lifecycle, RBAC, and CLI surface.
## Visibility scope (v7)
Virtual Llms and Agents now carry an explicit **visibility** field that
decides who can see the row in listings.
| Visibility | Meaning |
|-------------|----------------------------------------------------------------------------------|
| `public` | Visible to anyone with `view:llms` / `view:agents`. Default for hand-created Llms. |
| `private` | Only the **owner** plus principals with a name-scoped grant can see it. Default for virtual Llms and Agents on first publish. |
The owner is whichever user authenticated the publishing
`POST /api/v1/llms/_provider-register` (or `mcpctl create llm`). For
mcplocal that's whichever `~/.mcpctl/credentials` token is on disk.
Legacy rows from before v7 default to `visibility=public, ownerId=NULL`,
so the upgrade is a no-op for everything that already exists.
### Who skips the filter?
Two principals see every row regardless of visibility:
1. The **row owner** (`ownerId === request.userId`).
2. Anyone with a **cross-resource admin** grant — RBAC binding
`{ resource: '*' }`. Operationally this is the SRE / cluster admin.
A plain `view:llms` resource grant is *not* the same as admin: it's a
RBAC wildcard for name-scoping (you can name any Llm), but the
visibility filter still applies on top. This is the v7 split that
prevents a user with `view:llms` from enumerating every developer's
private virtual Llm.
### Granting a single-row exception
When alice wants bob to see her private virtual Llm `alice-vllm-local`
without making it public, she binds:
```sh
mcpctl create rbac bob view:llms --name alice-vllm-local
```
Same shape as any other name-scoped binding. Removing the binding
flips bob back to "row not found".
### Publishing as private from mcplocal
mcplocal defaults to `private` for every published provider and agent.
Override per-row in `~/.mcpctl/config.json`:
```jsonc
{
"llm": {
"providers": [
{ "name": "vllm-local", "type": "vllm", "model": "...", "publish": true,
"visibility": "private" }, // default; explicit for clarity
{ "name": "shared-qwen", "type": "vllm", "model": "...", "publish": true,
"visibility": "public" } // every team member can chat with it
]
},
"agents": [
{ "name": "local-coder", "llm": "vllm-local",
"visibility": "private" } // private agents pinned to private Llms
]
}
```
On a sticky reconnect (`providerSessionId` matches an existing row)
the visibility is **only** updated when the publisher explicitly sends
it — leaving the field off keeps whatever the row already has,
including any field admin set out-of-band.
### Hand-created Llms
`mcpctl create llm` defaults to `public` (matches pre-v7 behavior).
Pass `--visibility private` to opt in:
```sh
mcpctl create llm my-key --type openai --model gpt-4o \
--api-key-ref my-secret/key --visibility private
```
The same `--visibility` flag is on `mcpctl create agent`.
### CLI surface
`mcpctl get llm` and `mcpctl get agent` show a `VISIBILITY` column.
YAML round-trips cleanly: `mcpctl get llm X -o yaml | mcpctl apply -f -`
preserves visibility, and `ownerId` is stripped from the apply doc
because it's server-side state (the apply re-stamps the ownerId of the
authenticated caller, not the original creator).
## Roadmap (later stages)
(LB pool by name landed in v4; durable task queue landed in v5.)
- **v6** — multi-instance mcpd via pg `LISTEN/NOTIFY` (replaces the
(LB pool by name landed in v4; durable task queue landed in v5;
visibility scope landed in v7.)
- **v8** — multi-instance mcpd via pg `LISTEN/NOTIFY` (replaces the
per-instance EventEmitter wakeup), per-session worker capacity,
remote cancel protocol over the SSE channel.