refactor: add static source provider seam

This commit is contained in:
lda
2026-06-12 09:59:39 +07:00 Verified
parent dee95c89d2
commit bad3dbae0c
9 changed files with 148 additions and 17 deletions
+3
View File
@@ -96,6 +96,9 @@ auth admin are implemented. The next work is polish, not new broad surfaces.
`ops.py` source config, capability call, draft artifact creation, deployment, `ops.py` source config, capability call, draft artifact creation, deployment,
and workflow run. Runbook: and workflow run. Runbook:
[`Python source`](runbooks/python-source.md). [`Python source`](runbooks/python-source.md).
- Completed: static source inventory providers now have an explicit
`WorkflowSourceProvider.load_sources()` seam in `wf_server`, and Python
source loading is behind `PythonSourceProvider`.
- Completed: server startup policy moved to `wf_server.cli`; JSON-RPC HTTP - Completed: server startup policy moved to `wf_server.cli`; JSON-RPC HTTP
remains in `wf_transport_rpc_http`: remains in `wf_transport_rpc_http`:
[`server CLI and transport boundary`](superpowers/specs/2026-06-10-server-cli-transport-boundary.md). [`server CLI and transport boundary`](superpowers/specs/2026-06-10-server-cli-transport-boundary.md).
+13
View File
@@ -65,6 +65,18 @@ wf_config.server.sources[]
-> transport or CLI -> transport or CLI
``` ```
The first shared provider seam is intentionally static:
```python
class WorkflowSourceProvider(Protocol):
def load_sources(self) -> Mapping[str, CapabilitySource]: ...
```
This covers source families that can project configured inventory into
workflow-facing `CapabilitySource` objects. Provider-specific runtime pools,
admin/apply hooks, auth, catalog caches, and live health checks stay outside
this narrow seam until a source family needs them.
For MCP, the provider also owns stateful upstream sessions: For MCP, the provider also owns stateful upstream sessions:
```text ```text
@@ -78,6 +90,7 @@ For Python, the provider is simpler:
```text ```text
PythonSourceConfig(path, module, registry) PythonSourceConfig(path, module, registry)
-> PythonSourceProvider
-> import module -> import module
-> load NodeSpec registry -> load NodeSpec registry
-> qualify specs under source id -> qualify specs under source id
@@ -121,7 +121,8 @@ Implemented:
- `wf_config` accepts `server.sources[]` entries with `kind: "python"`. - `wf_config` accepts `server.sources[]` entries with `kind: "python"`.
- `wf_sources_python` loads trusted in-process `NodeSpec` registries from - `wf_sources_python` loads trusted in-process `NodeSpec` registries from
`path` plus `module:registry`. `path` plus `module:registry`.
- `wf_server.config` composes Python sources into local/static servers. - `wf_sources_python.PythonSourceProvider` implements the static
`WorkflowSourceProvider.load_sources()` seam used by `wf_server.config`.
- `wf config validate` imports configured trusted Python sources and reports - `wf config validate` imports configured trusted Python sources and reports
missing modules, missing registries, invalid registry shapes, and duplicate missing modules, missing registries, invalid registry shapes, and duplicate
specs before server startup. specs before server startup.
+14 -14
View File
@@ -3,9 +3,9 @@ from __future__ import annotations
from pathlib import Path from pathlib import Path
from wf_config import FilesystemStoreConfig, PythonSourceConfig, WorkflowConfigFile from wf_config import FilesystemStoreConfig, PythonSourceConfig, WorkflowConfigFile
from wf_platform import CapabilitySource
from .context import WorkflowServer, build_local_static_workflow_server from .context import WorkflowServer, build_local_static_workflow_server
from .sources import WorkflowSourceProvider, collect_static_sources
def _has_mcp_sources(config: WorkflowConfigFile) -> bool: def _has_mcp_sources(config: WorkflowConfigFile) -> bool:
@@ -14,20 +14,20 @@ def _has_mcp_sources(config: WorkflowConfigFile) -> bool:
) )
def _python_sources(config: WorkflowConfigFile) -> dict[str, CapabilitySource]: def _static_source_providers(
from wf_sources_python import load_python_source config: WorkflowConfigFile,
) -> list[WorkflowSourceProvider]:
return { providers: list[WorkflowSourceProvider] = []
source.id: load_python_source( python_configs = [
source_id=source.id, source
path=source.path,
module=source.module,
registry=source.registry,
enabled=source.enabled,
)
for source in config.server.sources for source in config.server.sources
if isinstance(source, PythonSourceConfig) if isinstance(source, PythonSourceConfig)
} ]
if python_configs:
from wf_sources_python import PythonSourceProvider
providers.append(PythonSourceProvider(python_configs))
return providers
def _build_mcp_workflow_server_from_workflow_config( def _build_mcp_workflow_server_from_workflow_config(
@@ -68,7 +68,7 @@ def build_workflow_server_from_workflow_config(
raise ValueError("wf-rpc-server currently requires filesystem store") raise ValueError("wf-rpc-server currently requires filesystem store")
return build_local_static_workflow_server( return build_local_static_workflow_server(
store.root, store.root,
extra_sources=_python_sources(config), extra_sources=collect_static_sources(_static_source_providers(config)),
) )
+48
View File
@@ -0,0 +1,48 @@
from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from typing import Protocol
from wf_platform import CapabilitySource
class WorkflowSourceProvider(Protocol):
"""Static source inventory provider for server composition.
This seam is intentionally narrow: providers return workflow-facing
`CapabilitySource` objects. Runtime pools, admin/apply hooks, and live
health checks remain separate provider-specific concerns.
"""
def load_sources(self) -> Mapping[str, CapabilitySource]: ...
@dataclass(frozen=True, slots=True)
class StaticSourceProvider:
"""Adapter for already-materialized capability sources."""
sources: Mapping[str, CapabilitySource]
def load_sources(self) -> Mapping[str, CapabilitySource]:
return dict(self.sources)
def collect_static_sources(
providers: Sequence[WorkflowSourceProvider],
) -> dict[str, CapabilitySource]:
"""Merge static provider inventories while rejecting ambiguous source ids."""
collected: dict[str, CapabilitySource] = {}
for provider in providers:
for source_id, source in provider.load_sources().items():
if source_id in collected:
raise ValueError(f"duplicate workflow source ids: {[source_id]}")
collected[source_id] = source
return collected
__all__ = [
"StaticSourceProvider",
"WorkflowSourceProvider",
"collect_static_sources",
]
+7 -1
View File
@@ -1,9 +1,15 @@
from __future__ import annotations from __future__ import annotations
from .loader import PythonSourceConfigLike, load_python_source, python_capability_source from .loader import (
PythonSourceConfigLike,
PythonSourceProvider,
load_python_source,
python_capability_source,
)
__all__ = [ __all__ = [
"PythonSourceConfigLike", "PythonSourceConfigLike",
"PythonSourceProvider",
"load_python_source", "load_python_source",
"python_capability_source", "python_capability_source",
] ]
+11
View File
@@ -2,6 +2,7 @@ from __future__ import annotations
import sys import sys
from collections.abc import Mapping, Sequence from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from importlib import import_module from importlib import import_module
from pathlib import Path from pathlib import Path
from typing import Any, Protocol from typing import Any, Protocol
@@ -23,6 +24,16 @@ class PythonSourceConfigLike(Protocol):
enabled: bool enabled: bool
@dataclass(frozen=True, slots=True)
class PythonSourceProvider:
"""Static source provider for trusted Python source config entries."""
configs: Sequence[PythonSourceConfigLike]
def load_sources(self) -> Mapping[str, CapabilitySource]:
return {config.id: python_capability_source(config) for config in self.configs}
def load_python_source( def load_python_source(
*, *,
source_id: str, source_id: str,
@@ -5,11 +5,24 @@ from pathlib import Path
import pytest import pytest
from wf_config import WorkflowConfigFile from wf_config import WorkflowConfigFile
from wf_platform import CapabilityBuckets, CapabilitySource
from wf_server.config import ( from wf_server.config import (
build_workflow_server_from_legacy_mcp_config, build_workflow_server_from_legacy_mcp_config,
build_workflow_server_from_workflow_config, build_workflow_server_from_workflow_config,
) )
from wf_server.context import WorkflowServer from wf_server.context import WorkflowServer
from wf_server.sources import StaticSourceProvider, collect_static_sources
class FakeSourceProvider:
def load_sources(self):
return {
"fake.ops": CapabilitySource(
id="fake.ops",
kind="python",
capabilities=CapabilityBuckets(),
)
}
def test_workflow_config_with_python_source_exposes_capability(tmp_path: Path) -> None: def test_workflow_config_with_python_source_exposes_capability(tmp_path: Path) -> None:
@@ -39,6 +52,27 @@ def test_workflow_config_with_python_source_exposes_capability(tmp_path: Path) -
) )
def test_static_source_provider_protocol_collects_sources() -> None:
sources = collect_static_sources([FakeSourceProvider()])
assert set(sources) == {"fake.ops"}
def test_static_source_provider_rejects_duplicate_source_ids() -> None:
provider = StaticSourceProvider(
{
"fake.ops": CapabilitySource(
id="fake.ops",
kind="python",
capabilities=CapabilityBuckets(),
)
}
)
with pytest.raises(ValueError, match="duplicate workflow source ids"):
collect_static_sources([provider, FakeSourceProvider()])
def test_build_workflow_server_from_workflow_config_uses_local_static_for_no_mcp_sources( def test_build_workflow_server_from_workflow_config_uses_local_static_for_no_mcp_sources(
tmp_path: Path, tmp_path: Path,
) -> None: ) -> None:
+16 -1
View File
@@ -4,7 +4,15 @@ from pathlib import Path
import pytest import pytest
from wf_sources_python import load_python_source from wf_sources_python import PythonSourceProvider, load_python_source
class PythonSourceConfigFixture:
id = "local.ops"
path = Path(".")
module = "tests.fixtures.python_source_ops"
registry = "registry"
enabled = True
def test_load_python_source_from_sequence_registry() -> None: def test_load_python_source_from_sequence_registry() -> None:
@@ -23,6 +31,13 @@ def test_load_python_source_from_sequence_registry() -> None:
assert source.permissions.safe_for_workflow is True assert source.permissions.safe_for_workflow is True
def test_python_source_provider_loads_configured_sources() -> None:
sources = PythonSourceProvider([PythonSourceConfigFixture()]).load_sources()
assert set(sources) == {"local.ops"}
assert "local.ops.echo" in sources["local.ops"].capabilities.node_specs
def test_load_python_source_from_callable_registry() -> None: def test_load_python_source_from_callable_registry() -> None:
source = load_python_source( source = load_python_source(
source_id="local.ops", source_id="local.ops",