fix: address source provider review notes

This commit is contained in:
lda
2026-06-12 10:56:51 +07:00 Verified
parent bad3dbae0c
commit 53a5111dc4
9 changed files with 46 additions and 23 deletions
+5 -4
View File
@@ -73,12 +73,13 @@ Add a `kind: "python"` entry under `server.sources[]`:
}
```
`path` is resolved relative to the config file. It is important for console
scripts: `uv run python` and installed entrypoints do not always have the same
import path.
`path` is resolved relative to the config file and added to `sys.path` before
import. This makes the module discoverable whether you run through
`uv run python` or an installed entrypoint.
The source id prefixes local names. A node named `echo` becomes
`local.ops.echo`. A node named `authoring.echo` is also exposed as
`local.ops.echo`. If a node uses the authoring namespace, such as
`authoring.echo`, that authoring prefix is replaced by the source id, producing
`local.ops.echo`.
## 3. Validate And Start
+2
View File
@@ -214,6 +214,8 @@ class WorkflowRunApi:
allowed = ", ".join(item.value for item in StoredRunStatus)
raise ValueError(f"status must be one of: {allowed}") from exc
# File-backed v1 stores keep run listing simple by filtering/sorting in
# memory. Move this into store-level pagination if run counts grow large.
records = self._run_store().list_runs()
if status_filter is not None:
records = [record for record in records if record.status == status_filter]
+1 -1
View File
@@ -27,7 +27,7 @@ def list_runs(
str | None,
typer.Option(
"--status",
help="Filter by stopped status: completed, failed, or interrupted.",
help="Filter by stopped status: completed, failed, interrupted, or blocked.",
),
] = None,
cursor: Annotated[
+3 -2
View File
@@ -14,11 +14,12 @@ from .models import (
def load_workflow_config(path: str | Path) -> WorkflowConfigFile:
"""Load neutral workflow config and resolve local filesystem paths.
"""Load neutral workflow config and resolve local filesystem/source paths.
Relative filesystem store roots are config-file relative so `wf --config`
behaves the same regardless of the caller's current working directory.
Role-specific store overrides follow the same rule.
Role-specific store overrides and Python source import paths follow the
same rule.
"""
config_path = Path(path)
+2
View File
@@ -8,6 +8,8 @@ from wf_core import ReducerSpec
from wf_core.runtime.ops.merges import ReducerDefinition
from wf_platform.refs import CapabilityRef
# Source kind is source-origin metadata, not a provider interface:
# system=built-ins, connection=upstream/stateful providers, python=trusted local code.
SourceKind = Literal["system", "connection", "python"]
JsonObject = dict[str, Any]
SOURCE_PREVIEW_LIMIT = 3
+5 -1
View File
@@ -35,8 +35,12 @@ def collect_static_sources(
collected: dict[str, CapabilitySource] = {}
for provider in providers:
for source_id, source in provider.load_sources().items():
if source.id != source_id:
raise ValueError(
f"provider source key {source_id!r} does not match source id {source.id!r}"
)
if source_id in collected:
raise ValueError(f"duplicate workflow source ids: {[source_id]}")
raise ValueError(f"duplicate workflow source ids: {source_id}")
collected[source_id] = source
return collected
+1 -3
View File
@@ -103,9 +103,7 @@ async def test_call_openapi_operation_maps_unexpected_status() -> None:
@pytest.mark.asyncio
async def test_call_openapi_operation_maps_invalid_request_to_validation_error() -> (
None
):
async def test_call_openapi_operation_maps_invalid_request_to_validation_error() -> None:
app = load_openapi_app(FIXTURE)
operation = next(
op for op in load_openapi_operations(FIXTURE) if op.name == "create_pet"
+12 -12
View File
@@ -15,9 +15,9 @@ from .conftest import structured
def test_proxy_admin_tools_mutate_config_file(tmp_path: Path) -> None:
tmp_path = tmp_path / "proxy_admin_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
test_root = tmp_path / "proxy_admin_store"
test_root.mkdir(parents=True, exist_ok=True)
config_path = test_root / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
@@ -91,9 +91,9 @@ def test_proxy_admin_tools_mutate_config_file(tmp_path: Path) -> None:
def test_proxy_admin_reload_remounts_connections(tmp_path: Path) -> None:
tmp_path = tmp_path / "proxy_reload_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
test_root = tmp_path / "proxy_reload_store"
test_root.mkdir(parents=True, exist_ok=True)
config_path = test_root / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
@@ -132,9 +132,9 @@ def test_proxy_admin_reload_remounts_connections(tmp_path: Path) -> None:
def test_proxy_admin_reload_sends_list_changed_notifications(tmp_path: Path) -> None:
tmp_path = tmp_path / "proxy_reload_notification_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
test_root = tmp_path / "proxy_reload_notification_store"
test_root.mkdir(parents=True, exist_ok=True)
config_path = test_root / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
@@ -166,9 +166,9 @@ def test_proxy_admin_reload_sends_list_changed_notifications(tmp_path: Path) ->
def test_proxy_config_mutation_does_not_notify_before_reload(tmp_path: Path) -> None:
tmp_path = tmp_path / "proxy_staged_notification_store"
tmp_path.mkdir(parents=True, exist_ok=True)
config_path = tmp_path / "wf_mcp.config.json"
test_root = tmp_path / "proxy_staged_notification_store"
test_root.mkdir(parents=True, exist_ok=True)
config_path = test_root / "wf_mcp.config.json"
config_path.write_text(
json.dumps(
{
@@ -73,6 +73,21 @@ def test_static_source_provider_rejects_duplicate_source_ids() -> None:
collect_static_sources([provider, FakeSourceProvider()])
def test_static_source_provider_rejects_source_key_mismatch() -> None:
provider = StaticSourceProvider(
{
"fake.alias": CapabilitySource(
id="fake.ops",
kind="python",
capabilities=CapabilityBuckets(),
)
}
)
with pytest.raises(ValueError, match="does not match source id"):
collect_static_sources([provider])
def test_build_workflow_server_from_workflow_config_uses_local_static_for_no_mcp_sources(
tmp_path: Path,
) -> None: