chore: address coderabbit cleanup notes

This commit is contained in:
lda
2026-06-04 15:58:54 +07:00 Unverified
parent 6b30a96ad7
commit b211eef1c7
21 changed files with 397 additions and 96 deletions
+3
View File
@@ -150,6 +150,9 @@ implementation state.
catalog snapshots are not mutated. Config-shadowed add is rejected in v1.
Remove requires `--confirm` in CLI; local/static servers report
unavailable.
- Cleanup candidate: consolidate store/source registry id validation patterns
(`SOURCE_REGISTRY_ID_PATTERN`, `STORE_ID_PATTERN`) only after another package
needs the same rule. Today they intentionally stay close to their stores.
- 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.
@@ -2,6 +2,16 @@
> **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.
### Target Selection Precedence
1. `--url` CLI override selects an RPC HTTP target.
2. `--local` CLI override selects the in-process local target.
3. Config file `client.target` selects the configured target.
4. Missing target config defaults to local.
CLI overrides intentionally win over config so one-off diagnostics can point at
a different server without editing the config file.
**Goal:** Add neutral workflow config models and let selected `wf` CLI commands target either local execution or the JSON-RPC HTTP server.
**Architecture:** Introduce `wf_config` as the protocol-neutral config package. Keep existing `wf_mcp.config.json` loading for compatibility, but add a new `wf.json`-style shape with `client.target`, `server.store`, `server.transports`, and bootstrap `server.sources`. Put the JSON-RPC client adapter in `wf_transport_rpc_http.client`; CLI context chooses local `WorkflowApi` or remote RPC adapter based on config plus CLI overrides.
@@ -0,0 +1,220 @@
# CodeRabbit Cleanup Follow-Ups 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:** Resolve non-blocking cleanup findings from the June 4 CodeRabbit review without mixing broad refactors into the focused correctness patch.
**Architecture:** Keep behavior unchanged unless a task explicitly says otherwise. Prefer documentation, small helpers, and narrowly scoped tests over cross-package refactors. Do not move modules or rename public APIs in this follow-up.
**Tech Stack:** Python 3.14, Pydantic v2, Typer, FastAPI JSON-RPC, pytest, ruff, basedpyright.
---
## Scope
This plan intentionally excludes the already-fixed review items:
- CLI config exception tuple syntax in `src/wf_cli/context.py`
- CLI source-registry file JSON object validation and exception chaining
- RPC method module structure-test coverage
- stale long-lived API and CLI/API alignment doc statuses
- typed `tmp_path` annotations in `tests/wf_config/test_config_models.py`
---
### Task 1: Document Source-Registry Mutation Asymmetry
**Files:**
- Modify: `docs/superpowers/plans/2026-06-04-source-registry-mutations.md`
- [ ] **Step 1: Add rationale under shadow handling**
Find the section mentioning:
```markdown
A future `allow_shadow` flag can relax add; do not add it in this slice.
```
Append:
```markdown
Rationale: `add` rejects config-shadowed ids to prevent silent no-ops: adding a
registry entry that cannot activate while config owns the same id. Existing
shadowed registry entries can still be updated, enabled, disabled, or removed so
operators can prepare store state for config removal or future `seed` ownership
policy.
```
- [ ] **Step 2: Verify doc diff**
Run:
```bash
git diff -- docs/superpowers/plans/2026-06-04-source-registry-mutations.md
```
Expected: only the rationale paragraph changed.
---
### Task 2: Clarify RPC Target Selection Precedence
**Files:**
- Modify: `docs/superpowers/plans/2026-06-03-workflow-config-and-rpc-cli-target.md`
- [ ] **Step 1: Add precedence table near the plan introduction**
Add:
```markdown
### Target Selection Precedence
1. `--url` CLI override selects an RPC HTTP target.
2. `--local` CLI override selects the in-process local target.
3. Config file `client.target` selects the configured target.
4. Missing target config defaults to local.
CLI overrides intentionally win over config so one-off diagnostics can point at
a different server without editing the config file.
```
- [ ] **Step 2: Verify no code references are changed**
Run:
```bash
git diff --stat
```
Expected: only the plan document is changed by this task.
---
### Task 3: Extract Source-Registry RPC Availability Helper
**Files:**
- Modify: `src/wf_transport_rpc_http/methods_source_registry.py`
- Test: `tests/wf_transport_rpc_http/test_source_registry_rpc.py`
- [ ] **Step 1: Add a helper**
In `src/wf_transport_rpc_http/methods_source_registry.py`, add:
```python
def _require_source_registry_admin(
server: WorkflowServer,
*,
operation: str,
) -> WorkflowSourceRegistrySurface:
admin = server.source_registry_admin
if admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": (
f"source registry admin {operation} are not available "
"for this server"
),
}
)
return admin
```
Import `WorkflowSourceRegistrySurface` from `wf_api` if needed.
- [ ] **Step 2: Replace repeated `None` checks**
Use:
```python
admin = _require_source_registry_admin(server, operation="reads")
```
for list/inspect, and:
```python
admin = _require_source_registry_admin(server, operation="mutations")
```
for add/update/enable/disable/remove.
- [ ] **Step 3: Run source-registry RPC tests**
Run:
```bash
uv run pytest tests/wf_transport_rpc_http/test_source_registry_rpc.py -q
```
Expected: all tests pass.
---
### Task 4: Clarify Chronological Event Ordering
**Files:**
- Modify: `src/wf_api/admin.py`
- Test: existing admin API tests if present
- [ ] **Step 1: Add a comment above `list_events`**
Add a short comment/docstring note near `WorkflowAdminApi.list_events`:
```python
# Preserve provider order for events; event providers are expected to return
# chronological order and callers may rely on that ordering for diagnostics.
```
Do not sort event payloads in this task.
- [ ] **Step 2: Run admin API tests**
Run:
```bash
uv run pytest tests/wf_api -q
```
Expected: all `wf_api` tests pass.
---
### Task 5: Record Shared ID Pattern Follow-Up
**Files:**
- Modify: `docs/current_roadmap.md`
- [ ] **Step 1: Add a small platform cleanup bullet**
Under the platform cleanup/architecture section, add:
```markdown
- Cleanup candidate: consolidate store/source registry id validation patterns
(`SOURCE_REGISTRY_ID_PATTERN`, `STORE_ID_PATTERN`) only after another package
needs the same rule. Today they intentionally stay close to their stores.
```
- [ ] **Step 2: Verify docs only**
Run:
```bash
git diff -- docs/current_roadmap.md
```
Expected: only the cleanup bullet changed.
---
## Final Verification
Run:
```bash
uv run pytest tests/wf_transport_rpc_http/test_source_registry_rpc.py tests/wf_api -q
uv run ruff check src/wf_transport_rpc_http/methods_source_registry.py src/wf_api/admin.py docs/superpowers/plans/2026-06-04-source-registry-mutations.md docs/superpowers/plans/2026-06-03-workflow-config-and-rpc-cli-target.md docs/current_roadmap.md
uv run basedpyright --level error src/wf_transport_rpc_http/methods_source_registry.py src/wf_api/admin.py
git diff --check
```
Expected: pytest exits 0, ruff exits 0, basedpyright exits 0, and `git diff --check` reports no whitespace errors.
@@ -42,6 +42,12 @@ For mutation v1:
- `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.
Rationale: `add` rejects config-shadowed ids to prevent silent no-ops: adding a
registry entry that cannot activate while config owns the same id. Existing
shadowed registry entries can still be updated, enabled, disabled, or removed so
operators can prepare store state for config removal or future `seed` ownership
policy.
### Full-Registry Validation
Every mutation must:
@@ -85,8 +85,11 @@ surface, or plain local CLI utilities.
and `wf source list` / `wf source inspect`.
- Read-only admin/config operations are now available through JSON-RPC HTTP
and `wf admin connections`, `wf admin statuses`, and `wf admin events`.
- Next source work is persistence for server-owned dynamic source changes.
- Keep mutation out until the store-backed source registry is designed.
- Source registry mutations (`add` / `update` / `enable` / `disable` /
`remove`) are now implemented for server-owned dynamic source changes.
- Remaining source-registry work is migration policy: config can bootstrap
or lock sources, while the store-backed registry owns mutable desired
state for dynamic sources.
2. **Mutable source/admin commands**
- Config can bootstrap sources, but server-owned dynamic source changes
@@ -2,7 +2,12 @@
Date: 2026-06-03
Status: design spec; implementation not started
Status: Slices 1-3 implemented. `wf_server` provides
`build_local_static_workflow_server`, `wf_transport_rpc_http` provides JSON-RPC
methods and client support, `wf_cli` has target-aware context, and `wf_config`
owns neutral config models. WebSocket transport, source providers, auth,
streaming/progress, database backend, and live MCP source management remain
future work.
Related:
@@ -92,12 +97,14 @@ It should prove:
Implementation status:
- `wf_server.build_local_static_workflow_server()` constructs a durable
- Slice 1 complete: `wf_server.build_local_static_workflow_server()` constructs a durable
`WorkflowApi` with required file-backed stores, local `wf.std`/`wf.recipes`
sources, and a local runtime runner.
- This first slice has no transport adapter. Clients still call the in-process
`WorkflowApi` in tests; HTTP/JSON-RPC/WebSocket/MCP transport adapters are
later slices.
- Slice 2 complete: `wf_transport_rpc_http` provides JSON-RPC 2.0 over HTTP via
`create_rpc_app(server)` and the `wf-rpc-server` CLI.
- Slice 3 complete: `wf_cli` supports target-aware context with `--local`,
`--url`, and `--timeout` overrides, and works with remote RPC targets for
capability and run commands.
First slice should not include:
@@ -279,10 +286,12 @@ Implementation status:
- `wf_transport_rpc_http.create_rpc_app(server)` exposes a fixed JSON-RPC
method set over an existing `wf_server.WorkflowServer`.
- `wf-rpc-server --store-root <path>` starts the local/static server over
`/rpc`.
- This slice still does not include remote `wf` CLI targeting, auth,
streaming/progress, or live upstream MCP source management.
- `wf-rpc-server --store-root <path>` and `wf-rpc-server --config <path>` start
the local/static server over `/rpc`.
- Remote `wf` CLI targeting is implemented through `wf_config` and target-aware
context in `wf_cli`.
- Auth, streaming/progress, and live upstream MCP source management remain
future work.
Preferred implementation dependency:
+2
View File
@@ -49,6 +49,8 @@ class WorkflowAdminApi:
)
return {"statuses": statuses, "total": len(statuses)}
# Preserve provider order for events; event providers are expected to return
# chronological order and callers may rely on that ordering for diagnostics.
async def list_events(self) -> dict[str, Any]:
events = [_payload(event) for event in self.events.list_events()]
return {"events": events, "total": len(events)}
+3 -1
View File
@@ -19,7 +19,9 @@ class WorkflowSourceRegistryProvider(Protocol):
class WorkflowSourceRegistryMutationProvider(Protocol):
"""Write capabilities for source registry mutation operations."""
def add_registry_entry(self, entry: Mapping[str, Any]) -> Mapping[str, Any] | object: ...
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: ...
+2 -2
View File
@@ -161,8 +161,8 @@ def _read_json_arg(
if file_path:
try:
value = json.loads(Path(file_path).read_text(encoding="utf-8"))
except FileNotFoundError:
raise typer.BadParameter(f"file not found: {file_path}")
except FileNotFoundError as exc:
raise typer.BadParameter(f"file not found: {file_path}") from exc
except json.JSONDecodeError as exc:
raise typer.BadParameter(f"invalid JSON in file: {exc}") from exc
return _require_json_object(value, flag_names)
+1 -1
View File
@@ -258,7 +258,7 @@ def _rpc_timeout_from_optional_config(
return override
try:
config = load_workflow_config(path)
except FileNotFoundError, json.JSONDecodeError, ValidationError:
except (FileNotFoundError, json.JSONDecodeError, ValidationError):
return 30.0
target = config.client.target
if isinstance(target, RpcHttpTargetConfig):
+8 -1
View File
@@ -3,7 +3,14 @@ from __future__ import annotations
from pathlib import Path
from typing import Annotated, Literal
from pydantic import AnyHttpUrl, BaseModel, ConfigDict, Field, field_validator, model_validator
from pydantic import (
AnyHttpUrl,
BaseModel,
ConfigDict,
Field,
field_validator,
model_validator,
)
class WorkflowConfigModel(BaseModel):
@@ -7,7 +7,11 @@ from typing import Any
from wf_api.source_registry_admin import WorkflowSourceRegistryMutationProvider
from ...models import ConnectionConfig
from ...source_registry import McpSourceRegistryEntry, SourceRegistryFile, SourceRegistryStore
from ...source_registry import (
McpSourceRegistryEntry,
SourceRegistryFile,
SourceRegistryStore,
)
@dataclass(slots=True)
@@ -38,7 +42,9 @@ class SourceRegistryAdminProvider(WorkflowSourceRegistryMutationProvider):
registry = SourceRegistryFile(sources=sources)
self.source_registry_store.save_registry(registry)
def _entry_map(self, registry: SourceRegistryFile) -> dict[str, McpSourceRegistryEntry]:
def _entry_map(
self, registry: SourceRegistryFile
) -> dict[str, McpSourceRegistryEntry]:
return registry.source_map()
def _require_entry(self, source_id: str) -> McpSourceRegistryEntry:
@@ -80,7 +86,9 @@ class SourceRegistryAdminProvider(WorkflowSourceRegistryMutationProvider):
self._save(sources)
return updated
def set_registry_entry_enabled(self, source_id: str, enabled: bool) -> McpSourceRegistryEntry:
def set_registry_entry_enabled(
self, source_id: str, enabled: bool
) -> McpSourceRegistryEntry:
existing = self._require_entry(source_id)
updated = existing.model_copy(update={"enabled": enabled})
registry = self._load()
+3 -1
View File
@@ -13,7 +13,9 @@ from .methods_capabilities import register_methods as register_capability_method
from .methods_deployments import register_methods as register_deployment_methods
from .methods_drafts import register_methods as register_draft_methods
from .methods_runs import register_methods as register_run_methods
from .methods_source_registry import register_methods as register_source_registry_methods
from .methods_source_registry import (
register_methods as register_source_registry_methods,
)
from .methods_sources import register_methods as register_source_methods
+5 -1
View File
@@ -9,7 +9,11 @@ import httpx
@dataclass(slots=True)
class RpcClientTransport:
"""Shared JSON-RPC request plumbing for workflow RPC client mixins."""
"""Shared JSON-RPC request plumbing for workflow RPC client mixins.
If `http_client` is provided, that client's own timeout configuration wins;
`timeout_seconds` is only used when this transport creates an `AsyncClient`.
"""
url: str
timeout_seconds: float = 30.0
@@ -6,6 +6,7 @@ from fastapi import Body
import fastapi_jsonrpc as jsonrpc
from fastapi_jsonrpc import Params
from wf_api import WorkflowSourceRegistrySurface
from wf_server import WorkflowServer
from .errors import WorkflowRpcError, raise_workflow_rpc_error
@@ -18,6 +19,25 @@ from .models import (
)
def _require_source_registry_admin(
server: WorkflowServer,
*,
operation: str,
) -> WorkflowSourceRegistrySurface:
admin = server.source_registry_admin
if admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": (
f"source registry admin {operation} are not available "
"for this server"
),
}
)
return admin
def register_methods(
entrypoint: jsonrpc.Entrypoint,
server: WorkflowServer,
@@ -33,15 +53,9 @@ def register_methods(
default_factory=ListRegistryEntriesParams,
),
) -> dict[str, Any]:
if server.source_registry_admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": "source registry admin reads are not available for this server",
}
)
admin = _require_source_registry_admin(server, operation="reads")
try:
return await server.source_registry_admin.list_registry_entries(
return await admin.list_registry_entries(
cursor=params.cursor,
limit=params.limit,
)
@@ -55,15 +69,9 @@ def register_methods(
async def workflow_admin_source_registry_inspect(
params: InspectRegistryEntryParams = Params(...), # type: ignore[reportArgumentType]
) -> dict[str, Any]:
if server.source_registry_admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": "source registry admin reads are not available for this server",
}
)
admin = _require_source_registry_admin(server, operation="reads")
try:
return await server.source_registry_admin.inspect_registry_entry(
return await admin.inspect_registry_entry(
source_id=params.source_id,
)
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
@@ -76,15 +84,9 @@ def register_methods(
async def workflow_admin_source_registry_add(
params: AddRegistryEntryParams = Params(...), # type: ignore[reportArgumentType]
) -> dict[str, Any]:
if server.source_registry_admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": "source registry admin mutations are not available for this server",
}
)
admin = _require_source_registry_admin(server, operation="mutations")
try:
return await server.source_registry_admin.add_registry_entry(
return await admin.add_registry_entry(
entry=params.entry,
)
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
@@ -97,15 +99,9 @@ def register_methods(
async def workflow_admin_source_registry_update(
params: UpdateRegistryEntryParams = Params(...), # type: ignore[reportArgumentType]
) -> dict[str, Any]:
if server.source_registry_admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": "source registry admin mutations are not available for this server",
}
)
admin = _require_source_registry_admin(server, operation="mutations")
try:
return await server.source_registry_admin.update_registry_entry(
return await admin.update_registry_entry(
source_id=params.source_id,
patch=params.patch,
)
@@ -119,15 +115,9 @@ def register_methods(
async def workflow_admin_source_registry_enable(
params: RegistryEntryIdParams = Params(...), # type: ignore[reportArgumentType]
) -> dict[str, Any]:
if server.source_registry_admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": "source registry admin mutations are not available for this server",
}
)
admin = _require_source_registry_admin(server, operation="mutations")
try:
return await server.source_registry_admin.enable_registry_entry(
return await admin.enable_registry_entry(
source_id=params.source_id,
)
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
@@ -140,15 +130,9 @@ def register_methods(
async def workflow_admin_source_registry_disable(
params: RegistryEntryIdParams = Params(...), # type: ignore[reportArgumentType]
) -> dict[str, Any]:
if server.source_registry_admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": "source registry admin mutations are not available for this server",
}
)
admin = _require_source_registry_admin(server, operation="mutations")
try:
return await server.source_registry_admin.disable_registry_entry(
return await admin.disable_registry_entry(
source_id=params.source_id,
)
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
@@ -161,15 +145,9 @@ def register_methods(
async def workflow_admin_source_registry_remove(
params: RegistryEntryIdParams = Params(...), # type: ignore[reportArgumentType]
) -> dict[str, Any]:
if server.source_registry_admin is None:
raise WorkflowRpcError(
data={
"code": "source_registry_unavailable",
"message": "source registry admin mutations are not available for this server",
}
)
admin = _require_source_registry_admin(server, operation="mutations")
try:
return await server.source_registry_admin.remove_registry_entry(
return await admin.remove_registry_entry(
source_id=params.source_id,
)
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
+35 -7
View File
@@ -93,7 +93,9 @@ def test_list_pagination() -> None:
)
first = asyncio.run(api.list_registry_entries(limit=2))
second = asyncio.run(api.list_registry_entries(cursor=first["next_cursor"], limit=2))
second = asyncio.run(
api.list_registry_entries(cursor=first["next_cursor"], limit=2)
)
assert [e["id"] for e in first["entries"]] == ["a", "b"]
assert first["next_cursor"] == "2"
@@ -162,7 +164,9 @@ class FakeMutationProvider:
self._entries.append(fe)
return asdict(fe)
def update_registry_entry(self, source_id: str, patch: Mapping[str, Any]) -> dict[str, Any]:
def update_registry_entry(
self, source_id: str, patch: Mapping[str, Any]
) -> dict[str, Any]:
for i, e in enumerate(self._entries):
if e.id == source_id:
merged = asdict(e)
@@ -171,7 +175,9 @@ class FakeMutationProvider:
return merged
raise KeyError(source_id)
def set_registry_entry_enabled(self, source_id: str, enabled: bool) -> dict[str, Any]:
def set_registry_entry_enabled(
self, source_id: str, enabled: bool
) -> dict[str, Any]:
for i, e in enumerate(self._entries):
if e.id == source_id:
merged = asdict(e)
@@ -193,12 +199,23 @@ def _mutation_api(
) -> tuple[WorkflowSourceRegistryApi, FakeMutationProvider]:
provider = FakeRegistryProvider(list(entries) if entries else [], config_ids)
mutation = FakeMutationProvider(list(entries) if entries else [])
return WorkflowSourceRegistryApi(provider=provider, mutation_provider=mutation), mutation
return WorkflowSourceRegistryApi(
provider=provider, mutation_provider=mutation
), mutation
def test_add_registry_entry() -> None:
api, _ = _mutation_api()
new_entry = {"id": "new.source", "kind": "mcp", "enabled": True, "provider": "new", "account": "default", "profile": None, "transport": {"kind": "stdio"}, "auth_ref": None}
new_entry = {
"id": "new.source",
"kind": "mcp",
"enabled": True,
"provider": "new",
"account": "default",
"profile": None,
"transport": {"kind": "stdio"},
"auth_ref": None,
}
payload = asyncio.run(api.add_registry_entry(entry=new_entry))
assert payload["entry"]["id"] == "new.source"
@@ -208,7 +225,16 @@ def test_add_registry_entry() -> None:
def test_add_registry_entry_shadowed() -> None:
api, _ = _mutation_api(config_ids={"new.source"})
new_entry = {"id": "new.source", "kind": "mcp", "enabled": True, "provider": "new", "account": "default", "profile": None, "transport": {"kind": "stdio"}, "auth_ref": None}
new_entry = {
"id": "new.source",
"kind": "mcp",
"enabled": True,
"provider": "new",
"account": "default",
"profile": None,
"transport": {"kind": "stdio"},
"auth_ref": None,
}
payload = asyncio.run(api.add_registry_entry(entry=new_entry))
assert payload["entry"]["id"] == "new.source"
@@ -219,7 +245,9 @@ def test_update_registry_entry() -> None:
api, _ = _mutation_api(
entries=[FakeRegistryEntry(id="upd.source", provider="old")],
)
payload = asyncio.run(api.update_registry_entry(source_id="upd.source", patch={"provider": "new"}))
payload = asyncio.run(
api.update_registry_entry(source_id="upd.source", patch={"provider": "new"})
)
assert payload["entry"]["id"] == "upd.source"
assert payload["entry"]["provider"] == "new"
+5 -2
View File
@@ -1,6 +1,7 @@
from __future__ import annotations
import json
from pathlib import Path
import pytest
from pydantic import ValidationError
@@ -113,7 +114,7 @@ def test_workflow_config_rejects_unwired_stdlib_source_id() -> None:
def test_load_workflow_config_resolves_filesystem_store_relative_to_config(
tmp_path,
tmp_path: Path,
) -> None:
config_path = tmp_path / "wf.json"
config_path.write_text(
@@ -133,7 +134,9 @@ def test_load_workflow_config_resolves_filesystem_store_relative_to_config(
assert config.server.store.root == (tmp_path / ".wf_store").resolve()
def test_load_workflow_config_preserves_absolute_filesystem_store(tmp_path) -> None:
def test_load_workflow_config_preserves_absolute_filesystem_store(
tmp_path: Path,
) -> None:
absolute_root = (tmp_path / "absolute-store").resolve()
config_path = tmp_path / "wf.json"
config_path.write_text(
+1 -3
View File
@@ -214,9 +214,7 @@ def test_server_reload_preserves_source_registry_connections() -> None:
client = create_server_client(config, config_path=config_path)
async with client:
before = await client.call_tool("wf.admin.list_sources", {"limit": 100})
before_ids = {
source["id"] for source in structured(before)["sources"]
}
before_ids = {source["id"] for source in structured(before)["sources"]}
assert "fixture.registry" in before_ids
await client.call_tool("wf.admin.reload_config")
@@ -241,7 +241,9 @@ def test_connection_service_sync_config_shadows_registry_entry() -> None:
)
def test_connection_service_sync_registry_disabled_entry_hydrates_disabled_source() -> None:
def test_connection_service_sync_registry_disabled_entry_hydrates_disabled_source() -> (
None
):
service = ConnectionService(events=BrokerEventRecorder(EventBus()))
catalog = _source_catalog(service)
store = FileSourceRegistryStore(local_temp_root() / "registry_disabled")
@@ -14,13 +14,17 @@ from wf_mcp.source_registry import (
)
def _store_with_entries(root: Path, *entries: McpSourceRegistryEntry) -> FileSourceRegistryStore:
def _store_with_entries(
root: Path, *entries: McpSourceRegistryEntry
) -> FileSourceRegistryStore:
store = FileSourceRegistryStore(root)
store.save_registry(SourceRegistryFile(sources=list(entries)))
return store
def _entry(source_id: str, *, provider: str = "github", account: str = "work") -> McpSourceRegistryEntry:
def _entry(
source_id: str, *, provider: str = "github", account: str = "work"
) -> McpSourceRegistryEntry:
return McpSourceRegistryEntry(
id=source_id,
provider=provider,
@@ -29,7 +33,9 @@ def _entry(source_id: str, *, provider: str = "github", account: str = "work") -
)
def _entry_dict(source_id: str, *, provider: str = "github", account: str = "work") -> dict:
def _entry_dict(
source_id: str, *, provider: str = "github", account: str = "work"
) -> dict:
return {
"id": source_id,
"provider": provider,
@@ -44,8 +50,13 @@ def _provider(
config_ids: frozenset[str] | None = None,
) -> SourceRegistryAdminProvider:
store = _store_with_entries(tmp_path / "reg", *(entries or []))
connections = [ConnectionConfig(id=cid, server="s", account="a") for cid in (config_ids or frozenset())]
return SourceRegistryAdminProvider(source_registry_store=store, config_connections=connections)
connections = [
ConnectionConfig(id=cid, server="s", account="a")
for cid in (config_ids or frozenset())
]
return SourceRegistryAdminProvider(
source_registry_store=store, config_connections=connections
)
# -- read tests ------------------------------------------------------------
@@ -197,7 +208,9 @@ def test_enable_disable_missing_source_raises_key_error(tmp_path: Path) -> None:
def test_remove_persists_absence_and_does_not_touch_unrelated(tmp_path: Path) -> None:
provider = _provider(tmp_path, entries=[_entry("keep.server"), _entry("drop.server")])
provider = _provider(
tmp_path, entries=[_entry("keep.server"), _entry("drop.server")]
)
result = provider.remove_registry_entry("drop.server")
@@ -6,11 +6,14 @@ from pathlib import Path
def test_rpc_transport_has_domain_method_modules() -> None:
for module_name in (
"wf_transport_rpc_http.methods_admin",
"wf_transport_rpc_http.methods_capabilities",
"wf_transport_rpc_http.methods_drafts",
"wf_transport_rpc_http.methods_artifacts",
"wf_transport_rpc_http.methods_deployments",
"wf_transport_rpc_http.methods_runs",
"wf_transport_rpc_http.methods_sources",
"wf_transport_rpc_http.methods_source_registry",
):
module = importlib.import_module(module_name)