feat(rbac): split the tasks namespace into tasks + inference-tasks

v5 gave the durable inference queue the RBAC resource `tasks`, on the
reasoning that inference was the only kind of task we had. Agentic teams
introduces work-item tasks (Epic -> epictask -> task), which need that name.

The queue moves to its own `inference-tasks` resource, matching its existing
/api/v1/inference-tasks route prefix, and `task`/`tasks` now mean work items.
Also lands the agentic-teams aliases: epic, epictask/et, repo/repository, session.

The rename is not purely a code change. ResourceBindingSchema runs
normalizeResource at parse time and stores the normalized string, so every
RbacDefinition row written before the split literally contains "tasks" meaning
inference. Shipping the alias change alone would silently stop those grants
authorizing the queue and start them authorizing work items -- a privilege
change in both directions. bootstrap/rbac-tasks-backfill.ts rewrites them once
at startup, alongside migrateAdminRole.

The run-once marker is claimed BEFORE the rewrite, not after. A failed marker
write after a successful rewrite would let the next startup rewrite again,
converting genuine post-split work-item grants into inference grants. Losing
the rewrite is recoverable by re-granting; running it twice silently is not --
so if the marker cannot be persisted we skip the rewrite and warn loudly.

mapUrlToPermission moves in lockstep: /api/v1/inference-tasks maps to
`inference-tasks`, otherwise the split would break queue authorization on the
first request.

Adds generic getBooleanSetting/setSetting helpers to bootstrap/system-settings.ts,
following the existing "global settings live in a Secret" pattern. setSetting
refuses to write when the settings secret has been migrated onto a non-plaintext
backend, where a direct `data` write would read back empty.

Out of scope, tracked separately: the CLI still routes `mcpctl get tasks` to the
inference queue (src/cli/src/commands/shared.ts). Repointing it needs the
work-item resource to exist first, or the command breaks with no replacement.

Tests: 14 unit tests cover alias resolution, the rewrite, wildcard and
operation bindings being left alone, idempotency, and both marker-failure paths.
Full workspace suite green (2291 tests). The new smoke test is RED until mcpd
is deployed with this change -- it asserts post-split storage against a live
daemon, and the currently deployed one predates the split.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N4wNHWf7xSwnZCWpJcyv9p
This commit is contained in:
Michal
2026-08-08 10:10:53 +01:00
parent 797df2299d
commit a186d95a85
7 changed files with 618 additions and 14 deletions

View File

@@ -154,7 +154,7 @@ mcpctl get task $ID
## RBAC
A new `tasks` resource. Default action mapping:
The `inference-tasks` resource. Default action mapping:
| Method | Action |
|--------|--------|
@@ -162,13 +162,22 @@ A new `tasks` resource. Default action mapping:
| `POST` | `create` |
| `DELETE` | `delete` |
Aliases: `task`, `inference-task`, `inference-tasks` all normalize to
`tasks` so `mcpctl create rbac-binding --resource task --role view ...`
works regardless of which form the operator types.
Aliases: `inference-task` and `inference-tasks` both normalize to
`inference-tasks`, so `mcpctl create rbac-binding --resource inference-task
--role view ...` works either way.
> **Renamed.** v5 shipped this queue under the RBAC resource `tasks`, on the
> reasoning that inference was the only kind of task we had. Agentic teams
> introduces work-item tasks, which take `tasks` (and the `task` alias) over.
> Bindings written before the split literally stored `"tasks"`, so mcpd rewrites
> them to `inference-tasks` once at startup — see
> `src/mcpd/src/bootstrap/rbac-tasks-backfill.ts`. Nothing to do by hand; the
> rewrite logs what it changed. `view:tasks` now grants **work items**, not
> inference.
Owner scoping is enforced at the route layer ON TOP of the RBAC hook:
- A caller with only the implicit role sees and can cancel their own tasks.
- `view:tasks` resource-wide is required to see / list / cancel
- `view:inference-tasks` resource-wide is required to see / list / cancel
another user's tasks.
## GC