10 KiB
Source Registry Admin Reads 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: Expose the persisted desired source registry through read-only admin APIs, JSON-RPC, and CLI without confusing it with observed runtime source inventory.
Architecture: Desired registry state is server/platform configuration. It is not workflow lifecycle state and not observed catalog/source inventory. Add a neutral read-only WorkflowSourceRegistryApi over a provider protocol in wf_api; implement the provider in wf_mcp using FileSourceRegistryStore and optional config connection ids for shadow information. Keep mutations out of scope.
Tech Stack: Python 3.14, Pydantic v2, wf_api, wf_mcp.source_registry, wf_transport_rpc_http, wf_cli, Typer, pytest, ruff, basedpyright.
Naming Decision
Use admin/config naming:
- JSON-RPC:
workflow.admin.source_registry.listworkflow.admin.source_registry.inspect
- CLI:
wf admin registry listwf admin registry inspect SOURCE_ID
Do not use wf source registry ... in this slice. wf source list already means observed/hydrated source inventory. Registry reads are desired server-owned configuration state.
Payload Shape
List payload:
{
"entries": [
{
"id": "github.work",
"kind": "mcp",
"enabled": true,
"provider": "github",
"account": "work",
"profile": null,
"transport_kind": "stdio",
"auth_ref": "github.work",
"shadowed_by_config": false
}
],
"next_cursor": null,
"total": 1
}
Inspect payload:
{
"entry": {
"id": "github.work",
"kind": "mcp",
"enabled": true,
"provider": "github",
"account": "work",
"profile": null,
"transport": {"kind": "stdio", "command": "npx", "args": [], "env": {}},
"auth_ref": "github.work",
"metadata": {}
},
"shadowed_by_config": false
}
Notes:
- List returns summaries; inspect returns full entry detail.
shadowed_by_configis advisory. If a caller has no config provider, returnfalserather than guessing.- Do not include auth secret payloads.
auth_refis only an id/reference.
Task 1: Add Neutral Source Registry Admin API
- Create
src/wf_api/source_registry_admin.py. - Define:
from __future__ import annotations
from collections.abc import Mapping, Sequence, Set
from dataclasses import asdict, is_dataclass
from typing import Any, Protocol
from wf_platform import page_items
class WorkflowSourceRegistryProvider(Protocol):
"""Provides desired source registry state for read-only admin frontends."""
def list_registry_entries(self) -> Sequence[Mapping[str, Any] | object]: ...
def config_source_ids(self) -> Set[str]: ...
- Define
WorkflowSourceRegistryApiwith:
async def list_registry_entries(
self,
*,
cursor: str | None = None,
limit: int = 50,
) -> dict[str, Any]: ...
async def inspect_registry_entry(self, *, source_id: str) -> dict[str, Any]: ...
- Normalize provider objects using the same style as
wf_api.admin._payload: mapping, dataclass, or Pydanticmodel_dump(mode="json"). - Add helpers:
def _entry_summary(entry: dict[str, Any], shadowed_ids: set[str]) -> dict[str, Any]:
transport = entry.get("transport")
transport_kind = transport.get("kind") if isinstance(transport, Mapping) else None
return {
"id": entry["id"],
"kind": entry["kind"],
"enabled": entry["enabled"],
"provider": entry.get("provider"),
"account": entry.get("account"),
"profile": entry.get("profile"),
"transport_kind": transport_kind,
"auth_ref": entry.get("auth_ref"),
"shadowed_by_config": entry["id"] in shadowed_ids,
}
inspect_registry_entry()should raiseKeyError(f"unknown registry source {source_id!r}")when missing.- Export
WorkflowSourceRegistryApiandWorkflowSourceRegistryProviderfromsrc/wf_api/__init__.py. - Add
WorkflowSourceRegistrySurfacetosrc/wf_api/surface.pyand__all__.
Tests
- Create
tests/wf_api/test_source_registry_admin_api.py. - Test list returns compact summaries in id order.
- Test pagination.
- Test inspect returns full entry and shadow flag.
- Test unknown inspect raises clear
KeyError. - Test the concrete API satisfies
WorkflowSourceRegistrySurface.
Task 2: Add MCP Provider for Registry Reads
- Create
src/wf_mcp/broker/service/source_registry_admin.py. - Define:
from __future__ import annotations
from collections.abc import Sequence
from dataclasses import dataclass, field
from ...models import ConnectionConfig
from ...source_registry import SourceRegistryStore
@dataclass(slots=True)
class SourceRegistryAdminProvider:
"""Read desired MCP source registry state without mutating it."""
source_registry_store: SourceRegistryStore
config_connections: Sequence[ConnectionConfig] = field(default_factory=tuple)
def list_registry_entries(self) -> list[object]:
return list(self.source_registry_store.load_registry().sources)
def config_source_ids(self) -> set[str]:
return {connection.id for connection in self.config_connections}
- Keep this provider read-only.
- Do not load auth records or catalog snapshots here.
Tests
- Create
tests/wf_mcp/service/test_source_registry_admin.py. - Test provider lists entries from
FileSourceRegistryStore. - Test provider reports config-shadowed ids.
Task 3: Wire Server Context
- Update
src/wf_server/context.py. - Add a nullable
source_registry_adminfield toWorkflowServer:
source_registry_admin: WorkflowSourceRegistryApi | None = None
- For the local/static server builder, leave it as
None. Local/static server has no file-backed MCP source registry. - This field is used by JSON-RPC registration; missing support should return a structured RPC error.
Tests
- Update
tests/wf_server/test_local_static_server.pyif needed to assert local/static construction still works.
Task 4: Wire MCP/CLI Server Construction
- Identify the current
WorkflowServerconstruction path for JSON-RPC HTTP target servers. - When constructing a server from broker config, create:
source_registry_admin = WorkflowSourceRegistryApi(
SourceRegistryAdminProvider(
source_registry_store=FileSourceRegistryStore(config.store_root),
config_connections=config.connections,
)
)
- Attach it to
WorkflowServer. - Do not alter source registry startup merge behavior in this slice.
Tests
- Add or update a JSON-RPC server construction test that seeds
source_registry.jsonand assertsserver.source_registry_adminis notNone.
Task 5: Add JSON-RPC Methods and Client Mixin
- Add
src/wf_transport_rpc_http/methods_source_registry.py. - Register:
workflow.admin.source_registry.list
workflow.admin.source_registry.inspect
- If
server.source_registry_admin is None, raiseWorkflowRpcErrorwith:
{
"code": "source_registry_unavailable",
"message": "source registry admin reads are not available for this server"
}
- Add
src/wf_transport_rpc_http/client_source_registry.py. - Add
RpcSourceRegistryClientMixinwith:
async def list_registry_entries(self, *, cursor: str | None = None, limit: int = 50) -> dict[str, Any]: ...
async def inspect_registry_entry(self, *, source_id: str) -> dict[str, Any]: ...
- Include the mixin in
src/wf_transport_rpc_http/client.py. - Register methods in
src/wf_transport_rpc_http/app.py.
Tests
- Add focused JSON-RPC method tests.
- Add client tests proving the mixin calls the correct method names.
- Add a local/static unavailable test.
Task 6: Add CLI Commands
- Add
src/wf_cli/commands/source_registry.py. - Register it under the existing
wf adminapp:
wf admin registry list
wf admin registry inspect SOURCE_ID
- Use the target-aware CLI context, not local-only context.
- Output JSON by default, following existing CLI command conventions.
- Do not add mutation flags.
Tests
- Add CLI tests:
- local/server target lists entries
- inspect returns full entry
- missing entry exits nonzero or returns structured error, matching current CLI patterns
Task 7: 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. - Add a short note to CLI docs if there is a current CLI command reference:
`wf admin registry list` shows desired persisted registry entries. `wf source list`
shows observed/hydrated source inventory. Use both when debugging disabled,
shadowed, or not-yet-hydrated sources.
Task 8: Verify
- Focused tests:
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:
uv run pytest tests/wf_transport_rpc_http tests/wf_cli -q
- Existing source registry tests:
uv run pytest tests/wf_api/test_source_registry.py tests/wf_mcp/test_source_registry.py -q
- Quality:
uv run ruff check src/wf_api src/wf_mcp src/wf_transport_rpc_http src/wf_cli tests/wf_api tests/wf_mcp tests/wf_transport_rpc_http tests/wf_cli
uv run basedpyright --level error src/wf_api src/wf_mcp src/wf_transport_rpc_http src/wf_cli tests/wf_api tests/wf_mcp tests/wf_transport_rpc_http tests/wf_cli
Acceptance Criteria
- Desired registry reads are available separately from observed source inventory.
- List is compact and paginated.
- Inspect returns full persisted entry details.
- Shadowed-by-config status is visible.
- Local/static servers report registry reads unavailable instead of pretending to have an empty registry.
- No mutation commands are added.
wf source listbehavior is unchanged.