AOI-1: drop instance-scope disable for plugins and MCP #730

Closed
opened 2026-05-02 11:01:45 +00:00 by claude-desktop · 0 comments
Collaborator

As an operator managing agent types and instances, I want the type-level capability surface (plugins, MCP servers) to be a non-negotiable contract, so that instances cannot silently break the role baseline by shadowing inherited entries with enabled=false.

Acceptance criteria

Resolver

  • resolvePlugins (apps/server/src/domain/agent-config/resolver.ts) ignores rows where scope='instance' AND enabled=false. The inherited row at agent_type / global / builtin continues to win.
  • resolveMcpServers ignores rows where scope='instance' AND enabled=false.
  • Resolver unit tests cover:
    • instance-scope enabled=false does NOT shadow inherited enabled row
    • instance-scope enabled=true for net-new (plugin_name|name) still works (additive — that is the whole point)

Write-path validation

  • Reject inserts/updates into plugin_binding where scope='instance' AND enabled=false. Error message: "instance scope cannot disable inherited plugin; remove at agent_type/global instead".
  • Reject inserts/updates into mcp_server where scope='instance' AND enabled=false. Error message: "instance scope cannot disable inherited MCP; remove at agent_type/global instead".
  • Validation lives in the same write path the dashboard uses (/config/plugins/binding, /config/mcp/server — adjust to actual route names).

Schema migration

  • New migration migrations/00X-add-only-inheritance.ts (number after current head).
  • Adds CHECK constraints as belt-and-suspenders:
ALTER TABLE plugin_binding ADD CONSTRAINT plugin_binding_no_instance_disable
  CHECK (NOT (scope = 'instance' AND enabled = 0));
ALTER TABLE mcp_server ADD CONSTRAINT mcp_server_no_instance_disable
  CHECK (NOT (scope = 'instance' AND enabled = 0));

Note: SQLite ALTER TABLE doesn't support adding CHECK constraints in-place. Migration must CREATE TABLE _new, copy rows, drop old, rename. Stale enabled=false rows at instance scope must be pruned BEFORE this migration runs (handled in AOI-5; sequence AOI-5 → AOI-1 in shipping order, OR migration coalesces both steps).

Tests

  • Resolver unit test for plugin shadow rule.
  • Resolver unit test for MCP shadow rule.
  • HTTP test: POST enabled=false at instance scope returns 400 with the exact error message.

Out of scope

  • Env-merge for MCP at instance scope — AOI-2.
  • Skill replace semantics doc — AOI-3.
  • Pruning existing stale rows — AOI-5 (must ship before AOI-1's CHECK constraint).
  • Dashboard UI changes — AOI-4.

References

  • apps/server/src/domain/agent-config/resolver.ts:240-355 (resolvePlugins, resolveMcpServers, mergeByKey)
  • Discussion 2026-05-02 — type = role contract, instance = additive only.
  • Sibling stories: AOI-2 (env merge), AOI-5 (prune stale rows — ships first).
As an operator managing agent types and instances, I want the type-level capability surface (plugins, MCP servers) to be a non-negotiable contract, so that instances cannot silently break the role baseline by shadowing inherited entries with `enabled=false`. ## Acceptance criteria ### Resolver - [ ] `resolvePlugins` (`apps/server/src/domain/agent-config/resolver.ts`) ignores rows where `scope='instance' AND enabled=false`. The inherited row at `agent_type` / `global` / `builtin` continues to win. - [ ] `resolveMcpServers` ignores rows where `scope='instance' AND enabled=false`. - [ ] Resolver unit tests cover: - instance-scope `enabled=false` does NOT shadow inherited enabled row - instance-scope `enabled=true` for net-new `(plugin_name|name)` still works (additive — that is the whole point) ### Write-path validation - [ ] Reject inserts/updates into `plugin_binding` where `scope='instance' AND enabled=false`. Error message: `"instance scope cannot disable inherited plugin; remove at agent_type/global instead"`. - [ ] Reject inserts/updates into `mcp_server` where `scope='instance' AND enabled=false`. Error message: `"instance scope cannot disable inherited MCP; remove at agent_type/global instead"`. - [ ] Validation lives in the same write path the dashboard uses (`/config/plugins/binding`, `/config/mcp/server` — adjust to actual route names). ### Schema migration - [ ] New migration `migrations/00X-add-only-inheritance.ts` (number after current head). - [ ] Adds `CHECK` constraints as belt-and-suspenders: ```sql ALTER TABLE plugin_binding ADD CONSTRAINT plugin_binding_no_instance_disable CHECK (NOT (scope = 'instance' AND enabled = 0)); ALTER TABLE mcp_server ADD CONSTRAINT mcp_server_no_instance_disable CHECK (NOT (scope = 'instance' AND enabled = 0)); ``` Note: SQLite ALTER TABLE doesn't support adding CHECK constraints in-place. Migration must `CREATE TABLE _new`, copy rows, drop old, rename. Stale `enabled=false` rows at instance scope must be pruned BEFORE this migration runs (handled in AOI-5; sequence AOI-5 → AOI-1 in shipping order, OR migration coalesces both steps). ### Tests - [ ] Resolver unit test for plugin shadow rule. - [ ] Resolver unit test for MCP shadow rule. - [ ] HTTP test: POST `enabled=false` at instance scope returns 400 with the exact error message. ## Out of scope - Env-merge for MCP at instance scope — AOI-2. - Skill replace semantics doc — AOI-3. - Pruning existing stale rows — AOI-5 (must ship before AOI-1's CHECK constraint). - Dashboard UI changes — AOI-4. ## References - `apps/server/src/domain/agent-config/resolver.ts:240-355` (`resolvePlugins`, `resolveMcpServers`, `mergeByKey`) - Discussion 2026-05-02 — type = role contract, instance = additive only. - Sibling stories: AOI-2 (env merge), AOI-5 (prune stale rows — ships first).
Sign in to join this conversation.
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks#730
No description provided.