docs: plan source registry mutations

This commit is contained in:
lda
2026-06-04 01:45:21 +07:00 Verified
parent dfa600b24a
commit c0a885f050
4 changed files with 374 additions and 4 deletions
+8 -4
View File
@@ -114,10 +114,9 @@ implementation state.
`WorkflowAdminSurface`, is exposed through JSON-RPC HTTP, and is available
through `wf admin connections`, `wf admin statuses`, and
`wf admin events`.
- Defer mutating source/connection config commands until the store-backed
source registry is designed. Config can bootstrap sources, but server-owned
dynamic source changes need persistence, validation, and auth rules before
they are safe.
- Mutating source/connection config commands now target the store-backed
source registry plan instead of config files. Config can bootstrap sources,
while server-owned dynamic source changes need validated registry writes.
- The store-backed source registry design is recorded in
[2026-06-03 store-backed source registry](./superpowers/specs/2026-06-03-store-backed-source-registry-design.md).
- First source registry implementation slices complete: validated registry
@@ -136,6 +135,11 @@ implementation state.
(`wf admin registry list` / `wf admin registry inspect`). Local/static
servers report unavailable instead of empty; a concrete MCP-backed
`WorkflowServer` construction path remains future work. No mutations added.
- Next executable source registry slice:
[2026-06-04 source registry mutations](./superpowers/plans/2026-06-04-source-registry-mutations.md).
It adds add/update/enable/disable/remove operations against desired
registry state only; config, auth records, and catalog snapshots are not
mutated in v1.
- Longer term: make the MCP frontend an adapter over these neutral workflow,
source-admin, and config-admin surfaces so the old `wf_mcp` server entry
point can shrink or retire.
@@ -68,6 +68,7 @@ The next executable slice is startup merge:
- Concrete MCP-backed `WorkflowServer` construction remains future work.
5. **Slice 5: Mutation Commands**
- **Status: planned.**
- Add add/update/enable/disable/remove operations.
- Use registry store, validation, and atomic writes.
- Keep auth/catalog cleanup deferred.
@@ -367,6 +368,9 @@ Pick one naming shape in the implementation plan.
Add safe registry mutation.
Implementation plan:
`docs/superpowers/plans/2026-06-04-source-registry-mutations.md`.
### Operations
- add source
@@ -0,0 +1,359 @@
# Source Registry Mutations Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add safe source registry mutation operations so server-owned MCP sources can be added, updated, enabled, disabled, and removed without editing config files by hand.
**Architecture:** Mutations target only the persisted desired registry, never config files. `wf_api` stays protocol-neutral by accepting/returning registry entry dictionaries. The MCP provider owns validation through `McpSourceRegistryEntry` / `SourceRegistryFile` and persistence through `SourceRegistryStore`. Runtime hydration still happens through the existing startup/reload merge path.
**Tech Stack:** Python 3.14, Pydantic v2, `wf_api.source_registry_admin`, `wf_mcp.source_registry`, `wf_transport_rpc_http`, `wf_cli`, Typer, pytest, ruff, basedpyright.
---
## Scope
Add these operations:
- add source registry entry
- update source registry entry
- enable source registry entry
- disable source registry entry
- remove source registry entry
Out of scope:
- no auth record creation/deletion
- no catalog snapshot deletion
- no config file mutation
- no live transport validation by default
- no raw proxy remount behavior changes
---
## Semantics
### Config Shadowing
Config-defined connections still win over registry entries during startup/reload.
For mutation v1:
- `add` rejects a source id that already exists in config to avoid silently adding a registry entry with no runtime effect.
- `update`, `enable`, `disable`, and `remove` may operate on existing registry entries even if they are currently shadowed by config.
- A future `allow_shadow` flag can relax `add`; do not add it in this slice.
### Full-Registry Validation
Every mutation must:
1. load the current registry
2. build a new `SourceRegistryFile`
3. validate the full file
4. save one atomic replacement
5. return the updated entry or removal payload
Never mutate the loaded registry object in-place and then save after partial validation. Build a new list first.
### Events
Mutations should emit events if an event recorder is available:
- `source_registry_entry_added`
- `source_registry_entry_updated`
- `source_registry_entry_enabled`
- `source_registry_entry_disabled`
- `source_registry_entry_removed`
Payload should include at least:
```json
{"source_id": "github.work", "provider": "github", "account": "work"}
```
If adding event wiring is awkward for this slice, keep mutation payloads correct and document event wiring as future work. Do not block mutation safety on event polish.
---
## Task 1: Extend Neutral API Provider Protocol
- [ ] Update `src/wf_api/source_registry_admin.py`.
- [ ] Add mutation methods to `WorkflowSourceRegistryProvider`:
```python
def add_registry_entry(self, entry: Mapping[str, Any]) -> Mapping[str, Any] | object: ...
def update_registry_entry(
self,
source_id: str,
patch: Mapping[str, Any],
) -> Mapping[str, Any] | object: ...
def set_registry_entry_enabled(
self,
source_id: str,
enabled: bool,
) -> Mapping[str, Any] | object: ...
def remove_registry_entry(self, source_id: str) -> Mapping[str, Any] | object: ...
```
- [ ] Add async API methods:
```python
async def add_registry_entry(self, *, entry: dict[str, Any]) -> dict[str, Any]: ...
async def update_registry_entry(
self,
*,
source_id: str,
patch: dict[str, Any],
) -> dict[str, Any]: ...
async def enable_registry_entry(self, *, source_id: str) -> dict[str, Any]: ...
async def disable_registry_entry(self, *, source_id: str) -> dict[str, Any]: ...
async def remove_registry_entry(self, *, source_id: str) -> dict[str, Any]: ...
```
- [ ] Return shapes:
```python
{"entry": <full entry>, "shadowed_by_config": bool}
{"removed": true, "source_id": "..."}
```
- [ ] Reuse existing `_payload()` and shadow helper logic.
- [ ] Update `WorkflowSourceRegistrySurface` in `src/wf_api/surface.py`.
### Tests
- [ ] Extend `tests/wf_api/test_source_registry_admin_api.py`.
- [ ] Add fake mutable provider.
- [ ] Test each API method delegates and returns normalized payloads.
- [ ] Test remove payload.
---
## Task 2: Implement MCP Mutation Provider
- [ ] Update `src/wf_mcp/broker/service/source_registry_admin.py`.
- [ ] Keep `SourceRegistryAdminProvider` as the read/write provider.
- [ ] Add optional event sink if practical:
```python
from collections.abc import Callable
from ...events import McpEvent
event_sink: Callable[[McpEvent], None] | None = None
```
If this creates too much coupling, skip event sink and document it.
- [ ] Add helpers:
```python
def _load(self) -> SourceRegistryFile: ...
def _save(self, sources: list[McpSourceRegistryEntry]) -> SourceRegistryFile: ...
def _entry_map(self, registry: SourceRegistryFile) -> dict[str, McpSourceRegistryEntry]: ...
def _require_entry(self, source_id: str) -> McpSourceRegistryEntry: ...
```
- [ ] `add_registry_entry(entry)`:
- reject if `entry["id"]` is in `config_source_ids()`
- validate with `McpSourceRegistryEntry.model_validate(entry)`
- reject duplicate existing registry id
- save full `SourceRegistryFile`
- return added entry
- [ ] `update_registry_entry(source_id, patch)`:
- require existing registry entry
- reject changing `id` in v1 unless it equals `source_id`
- merge existing full JSON entry with patch
- validate with `McpSourceRegistryEntry`
- save full file
- return updated entry
- [ ] `set_registry_entry_enabled(source_id, enabled)`:
- require existing registry entry
- update `enabled`
- save full file
- return updated entry
- [ ] `remove_registry_entry(source_id)`:
- require existing registry entry
- save full file without the entry
- return `{"removed": True, "source_id": source_id}`
- do not delete auth/catalog
### Tests
- [ ] Extend or create tests in `tests/wf_mcp/service/test_source_registry_admin.py`.
- [ ] Test add persists and round-trips through store.
- [ ] Test add rejects config-shadowed id.
- [ ] Test add rejects duplicate registry id.
- [ ] Test update persists provider/account/transport changes.
- [ ] Test update rejects id change.
- [ ] Test enable/disable persist.
- [ ] Test remove persists absence and does not touch unrelated entries.
- [ ] Test missing source raises clear `KeyError`.
- [ ] Test malformed payload raises validation error with actionable message.
---
## Task 3: Add RPC Mutation Methods
- [ ] Update `src/wf_transport_rpc_http/models.py`.
- [ ] Add params:
```python
class AddRegistryEntryParams(RpcParamsModel):
entry: dict[str, Any]
class UpdateRegistryEntryParams(RpcParamsModel):
source_id: str = Field(min_length=1)
patch: dict[str, Any]
class RegistryEntryIdParams(RpcParamsModel):
source_id: str = Field(min_length=1)
```
- [ ] Update `src/wf_transport_rpc_http/methods_source_registry.py`.
- [ ] Register:
```text
workflow.admin.source_registry.add
workflow.admin.source_registry.update
workflow.admin.source_registry.enable
workflow.admin.source_registry.disable
workflow.admin.source_registry.remove
```
- [ ] Keep unavailable behavior identical to read methods:
```json
{"code": "source_registry_unavailable", ...}
```
- [ ] Use `Params(...)` for required params, matching inspect methods.
- [ ] Update `src/wf_transport_rpc_http/client_source_registry.py`.
- [ ] Add matching client methods.
### Tests
- [ ] Extend `tests/wf_transport_rpc_http/test_source_registry_rpc.py`.
- [ ] Add positive method tests using a fake `WorkflowServer` with `WorkflowSourceRegistryApi`.
- [ ] Add unavailable mutation test on local/static server.
- [ ] Add client method tests for method names and payloads.
---
## Task 4: Add CLI Mutation Commands
- [ ] Update `src/wf_cli/commands/source_registry.py`.
- [ ] Add commands:
```bash
wf admin registry add --input '{"id":"github.work",...}'
wf admin registry update SOURCE_ID --patch '{"enabled":false}'
wf admin registry enable SOURCE_ID
wf admin registry disable SOURCE_ID
wf admin registry remove SOURCE_ID
```
- [ ] Support `--input-file` for add and `--patch-file` for update if existing CLI helpers make it easy.
- [ ] JSON inline input is required for v1. Do not design a large flag matrix for every MCP field yet.
- [ ] For remove, require `--confirm` to avoid accidental deletion:
```bash
wf admin registry remove github.work --confirm
```
- [ ] If `source_registry_admin is None`, keep the existing "not available" behavior.
### Tests
- [ ] Extend `tests/wf_cli/test_source_registry.py`.
- [ ] Test help includes add/update/enable/disable/remove.
- [ ] Test local/static unavailable for one mutation command.
- [ ] Test remove without `--confirm` fails.
- [ ] Test commands delegate to a fake context where possible, or use RPC client monkeypatch patterns already used in CLI tests.
---
## Task 5: Wire Real MCP Server Provider If Available
Current state: `WorkflowServer.source_registry_admin` exists, but local/static servers set it to `None`. There may still be no concrete MCP-backed `WorkflowServer` construction path.
- [ ] If a concrete MCP-backed `WorkflowServer` construction path exists, wire:
```python
WorkflowSourceRegistryApi(
provider=SourceRegistryAdminProvider(
source_registry_store=FileSourceRegistryStore(config.store_root),
config_connections=config.connections,
)
)
```
- [ ] If no such path exists, do not invent it in this slice. Keep docs explicit that RPC/CLI mutation methods require a target exposing `source_registry_admin`.
---
## Task 6: Docs
- [ ] Update `docs/current_roadmap.md`.
- [ ] Update `docs/superpowers/specs/2026-06-03-store-backed-source-registry-design.md`.
- [ ] Update `docs/superpowers/plans/2026-06-03-source-registry-next-slices.md`.
- [ ] Document:
- registry mutations change desired state only
- config files are not mutated
- auth/catalog are not deleted on remove
- startup/reload is still how runtime hydration sees changes
- config-shadowed add is rejected in v1
---
## Task 7: Verify
- [ ] Focused API/provider tests:
```bash
uv run pytest tests/wf_api/test_source_registry_admin_api.py tests/wf_mcp/service/test_source_registry_admin.py -q
```
- [ ] RPC/CLI tests:
```bash
uv run pytest tests/wf_transport_rpc_http/test_source_registry_rpc.py tests/wf_cli/test_source_registry.py -q
```
- [ ] Existing registry/startup tests:
```bash
uv run pytest tests/wf_api/test_source_registry.py tests/wf_mcp/test_source_registry.py tests/wf_mcp/service/test_connection_service.py tests/wf_mcp/test_broker_server.py -q
```
- [ ] Quality:
```bash
uv run ruff check src/wf_api/source_registry_admin.py src/wf_mcp/broker/service/source_registry_admin.py src/wf_transport_rpc_http src/wf_cli tests/wf_api/test_source_registry_admin_api.py tests/wf_mcp/service/test_source_registry_admin.py tests/wf_transport_rpc_http/test_source_registry_rpc.py tests/wf_cli/test_source_registry.py
uv run basedpyright --level error src/wf_api/source_registry_admin.py src/wf_mcp/broker/service/source_registry_admin.py src/wf_transport_rpc_http src/wf_cli tests/wf_api/test_source_registry_admin_api.py tests/wf_mcp/service/test_source_registry_admin.py tests/wf_transport_rpc_http/test_source_registry_rpc.py tests/wf_cli/test_source_registry.py
```
---
## Acceptance Criteria
- Registry add/update/enable/disable/remove work through neutral API methods.
- MCP provider validates all writes through `McpSourceRegistryEntry` / `SourceRegistryFile`.
- Whole registry is validated before every save.
- Config files are not mutated.
- Auth/catalog files are not deleted.
- Adding an id owned by config is rejected in v1.
- Existing shadowed registry entries can still be updated or removed.
- RPC methods and CLI commands exist.
- Remove requires explicit CLI confirmation.
- Observed source inventory behavior is unchanged until reload/startup rehydrates runtime state.
@@ -280,6 +280,9 @@ MCP-backed `WorkflowServer` construction remains future work.
### Slice 5: Mutating RPC/CLI
Implementation plan:
[2026-06-04 source registry mutations](../plans/2026-06-04-source-registry-mutations.md).
- Add add/update/enable/disable/remove operations.
- Add JSON-RPC methods.
- Add CLI commands.