the Stores
This commit is contained in:
@@ -112,11 +112,12 @@ implementation state.
|
|||||||
inventory and deployment metadata instead of reverse-engineering MCP tools.
|
inventory and deployment metadata instead of reverse-engineering MCP tools.
|
||||||
- **Workflow API extraction**: continue the staged extraction in
|
- **Workflow API extraction**: continue the staged extraction in
|
||||||
[wf_api extraction roadmap](./superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md).
|
[wf_api extraction roadmap](./superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md).
|
||||||
The next useful slice is scaffolding a protocol-neutral operation context for
|
Protocol-neutral operation context and domain services now exist behind
|
||||||
stores, capability sources, event recording, and live source calls. Only after
|
`wf_api`; MCP tool schemas and tool registration stay in `wf_mcp`.
|
||||||
that seam exists should the large `WorkflowSurfaceHandlers` implementation be
|
- Workflow store ownership is explicit: entrypoints construct/inject `WorkflowStores`; `WfMcpService` no longer guesses stores from the MCP store root.
|
||||||
split by domain behind `wf_api`; MCP tool schemas and tool registration stay
|
- The next useful slice is removing the remaining double-delegation path so
|
||||||
in `wf_mcp`.
|
`WorkflowApi` composes domain services directly instead of routing through
|
||||||
|
`WfMcpWorkflowApiBackend` and `WorkflowSurfaceHandlers`.
|
||||||
|
|
||||||
Frame stress points remaining for native subgraphs and future fork/gather:
|
Frame stress points remaining for native subgraphs and future fork/gather:
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,505 @@
|
|||||||
|
# wf_api Store Ownership 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:** Make workflow artifact, draft workspace, and run store ownership explicit so `wf_api`, CLI, MCP, and future HTTP entrypoints can share stores without relying on `WfMcpService.__post_init__`.
|
||||||
|
|
||||||
|
**Architecture:** Add a protocol-neutral `WorkflowStores` bundle in `wf_api` that groups the three workflow stores. MCP config construction remains responsible for creating file-backed stores from `BrokerConfig.store_root`; `WfMcpService` receives stores but no longer manufactures them from its MCP `Store`. Existing process-local behavior stays intact through `build_service_from_config`.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.14, dataclasses, `wf_api`, `wf_artifacts`, `wf_mcp`, pytest, ruff, basedpyright.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Problem
|
||||||
|
|
||||||
|
`src/wf_mcp/broker/service/core.py` currently does this in `WfMcpService.__post_init__`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if self.artifact_store is None:
|
||||||
|
self.artifact_store = FileWorkflowArtifactStore(_store_root(self.store))
|
||||||
|
if self.draft_workspace_store is None:
|
||||||
|
self.draft_workspace_store = FileDraftWorkspaceStore(_store_root(self.store))
|
||||||
|
if self.run_store is None:
|
||||||
|
self.run_store = FileRunStore(_store_root(self.store))
|
||||||
|
```
|
||||||
|
|
||||||
|
That makes a protocol-specific service decide protocol-neutral workflow persistence. It also hides missing-store tests because `WfMcpService(store=FileStore(...))` silently creates workflow stores.
|
||||||
|
|
||||||
|
`src/wf_mcp/broker/config.py::build_service_from_config` already does the right thing by passing all three stores explicitly. This slice preserves that behavior and removes the fallback.
|
||||||
|
|
||||||
|
## Target Ownership Rule
|
||||||
|
|
||||||
|
- `wf_artifacts` owns store protocols and file store implementations.
|
||||||
|
- `wf_api` may group protocol-neutral workflow stores into a small DTO.
|
||||||
|
- `wf_mcp` owns MCP config loading and calls the DTO factory for file-backed process-local stores.
|
||||||
|
- `WfMcpService` owns broker state, connections, adapters, source catalogs, events, and execution wiring.
|
||||||
|
- `WfMcpService` does not create workflow artifact/draft/run stores by guessing from the MCP catalog/auth store.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- Create: `src/wf_api/stores.py`
|
||||||
|
- Modify: `src/wf_api/__init__.py`
|
||||||
|
- Modify: `src/wf_mcp/broker/config.py`
|
||||||
|
- Modify: `src/wf_mcp/broker/service/core.py`
|
||||||
|
- Modify: direct `WfMcpService(...)` tests only where they rely on implicit workflow stores
|
||||||
|
- Test: `tests/wf_api/test_stores.py`
|
||||||
|
- Test: `tests/wf_mcp/service/test_catalog.py`
|
||||||
|
- Test: `tests/wf_mcp/test_broker_server.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Add Protocol-Neutral Store Bundle
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/wf_api/stores.py`
|
||||||
|
- Modify: `src/wf_api/__init__.py`
|
||||||
|
- Test: `tests/wf_api/test_stores.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing store bundle tests**
|
||||||
|
|
||||||
|
Create `tests/wf_api/test_stores.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from wf_api.stores import WorkflowStores, file_workflow_stores
|
||||||
|
from wf_artifacts import (
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
)
|
||||||
|
|
||||||
|
from tests.wf_mcp.test_support import local_temp_root
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_workflow_stores_constructs_all_three_file_stores() -> None:
|
||||||
|
root = local_temp_root() / "wf_api_file_workflow_stores"
|
||||||
|
|
||||||
|
stores = file_workflow_stores(root)
|
||||||
|
|
||||||
|
assert isinstance(stores, WorkflowStores)
|
||||||
|
assert isinstance(stores.artifact_store, FileWorkflowArtifactStore)
|
||||||
|
assert isinstance(stores.draft_workspace_store, FileDraftWorkspaceStore)
|
||||||
|
assert isinstance(stores.run_store, FileRunStore)
|
||||||
|
assert stores.artifact_store.root == root
|
||||||
|
assert stores.draft_workspace_store.root == root
|
||||||
|
assert stores.run_store.root == root
|
||||||
|
|
||||||
|
|
||||||
|
def test_wf_api_exports_workflow_stores() -> None:
|
||||||
|
from wf_api import WorkflowStores as ExportedWorkflowStores
|
||||||
|
from wf_api import file_workflow_stores as exported_file_workflow_stores
|
||||||
|
|
||||||
|
assert ExportedWorkflowStores is WorkflowStores
|
||||||
|
assert exported_file_workflow_stores is file_workflow_stores
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run failing tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_api\test_stores.py -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: import failure for `wf_api.stores`.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement `wf_api.stores`**
|
||||||
|
|
||||||
|
Create `src/wf_api/stores.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from wf_artifacts import (
|
||||||
|
DraftWorkspaceStore,
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
RunStore,
|
||||||
|
WorkflowArtifactStore,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class WorkflowStores:
|
||||||
|
"""Protocol-neutral persistence dependencies for workflow APIs."""
|
||||||
|
|
||||||
|
artifact_store: WorkflowArtifactStore
|
||||||
|
draft_workspace_store: DraftWorkspaceStore
|
||||||
|
run_store: RunStore
|
||||||
|
|
||||||
|
|
||||||
|
def file_workflow_stores(root: str | Path) -> WorkflowStores:
|
||||||
|
"""Create process-local file-backed workflow stores under one root."""
|
||||||
|
store_root = Path(root)
|
||||||
|
return WorkflowStores(
|
||||||
|
artifact_store=FileWorkflowArtifactStore(store_root),
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(store_root),
|
||||||
|
run_store=FileRunStore(store_root),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["WorkflowStores", "file_workflow_stores"]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Export from `wf_api`**
|
||||||
|
|
||||||
|
Update `src/wf_api/__init__.py`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from .stores import WorkflowStores, file_workflow_stores
|
||||||
|
```
|
||||||
|
|
||||||
|
Add both names to `__all__`.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Verify Task 1**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_api\test_stores.py -q
|
||||||
|
uv run ruff check src\wf_api\stores.py tests\wf_api\test_stores.py
|
||||||
|
uv run ruff format --check src\wf_api\stores.py tests\wf_api\test_stores.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: tests pass, lint pass, format pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Move Config Store Construction Through the Bundle
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_mcp/broker/config.py`
|
||||||
|
- Test: `tests/wf_mcp/test_broker_server.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Strengthen config construction test**
|
||||||
|
|
||||||
|
Find `tests/wf_mcp/test_broker_server.py::test_build_service_from_config_uses_store_root_for_artifacts`.
|
||||||
|
|
||||||
|
Update it to assert all three stores use the configured root:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_build_service_from_config_uses_store_root_for_workflow_stores() -> None:
|
||||||
|
store_root = local_temp_root() / "broker_config_workflow_stores"
|
||||||
|
config = BrokerConfig(store_root=store_root, connections=[])
|
||||||
|
|
||||||
|
service = build_service_from_config(config)
|
||||||
|
|
||||||
|
assert isinstance(service.artifact_store, FileWorkflowArtifactStore)
|
||||||
|
assert isinstance(service.draft_workspace_store, FileDraftWorkspaceStore)
|
||||||
|
assert isinstance(service.run_store, FileRunStore)
|
||||||
|
assert service.artifact_store.root == store_root
|
||||||
|
assert service.draft_workspace_store.root == store_root
|
||||||
|
assert service.run_store.root == store_root
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure the test imports:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_artifacts import FileDraftWorkspaceStore, FileRunStore, FileWorkflowArtifactStore
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the focused test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_mcp\test_broker_server.py::test_build_service_from_config_uses_store_root_for_workflow_stores -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: pass before the config refactor, proving current behavior is covered.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Update `build_service_from_config` to use `file_workflow_stores`**
|
||||||
|
|
||||||
|
In `src/wf_mcp/broker/config.py`, replace direct file store imports:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_api import file_workflow_stores
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_artifacts import (
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Then update `build_service_from_config`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def build_service_from_config(config: BrokerConfig) -> WfMcpService:
|
||||||
|
"""Create a broker service with SDK adapters for configured connections."""
|
||||||
|
runtime_factory = PersistentSessionFactory()
|
||||||
|
workflow_stores = file_workflow_stores(config.store_root)
|
||||||
|
service = WfMcpService(
|
||||||
|
store=FileStore(config.store_root),
|
||||||
|
artifact_store=workflow_stores.artifact_store,
|
||||||
|
draft_workspace_store=workflow_stores.draft_workspace_store,
|
||||||
|
run_store=workflow_stores.run_store,
|
||||||
|
# Discovery can use short-lived SDK sessions. Workflow execution needs
|
||||||
|
# a persistent runtime so stateful MCP servers keep session/page state
|
||||||
|
# across sequential workflow nodes.
|
||||||
|
tool_executor=McpRuntimePool(runtime_factory.create),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify Task 2**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_mcp\test_broker_server.py::test_build_service_from_config_uses_store_root_for_workflow_stores -q
|
||||||
|
uv run ruff check src\wf_mcp\broker\config.py tests\wf_mcp\test_broker_server.py
|
||||||
|
uv run ruff format --check src\wf_mcp\broker\config.py tests\wf_mcp\test_broker_server.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: tests pass, lint pass, format pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Remove Implicit Workflow Store Creation from WfMcpService
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_mcp/broker/service/core.py`
|
||||||
|
- Modify: `tests/wf_mcp/service/test_catalog.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace the old default-store test**
|
||||||
|
|
||||||
|
Find `tests/wf_mcp/service/test_catalog.py::test_service_installs_default_draft_workspace_store`.
|
||||||
|
|
||||||
|
Replace it with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def test_service_does_not_install_workflow_stores_implicitly() -> None:
|
||||||
|
root = local_temp_root() / "service_no_implicit_workflow_stores"
|
||||||
|
service = WfMcpService(store=FileStore(root))
|
||||||
|
|
||||||
|
assert service.artifact_store is None
|
||||||
|
assert service.draft_workspace_store is None
|
||||||
|
assert service.run_store is None
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run failing test**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_mcp\service\test_catalog.py::test_service_does_not_install_workflow_stores_implicitly -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: fail because `WfMcpService.__post_init__` still creates stores.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Remove implicit creation from `WfMcpService.__post_init__`**
|
||||||
|
|
||||||
|
In `src/wf_mcp/broker/service/core.py`, remove the imports:
|
||||||
|
|
||||||
|
```python
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove the `_store_root` helper entirely:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _store_root(store: Store) -> Path:
|
||||||
|
"""Return the file root for stores that expose one, else use local default."""
|
||||||
|
root = getattr(store, "root", None)
|
||||||
|
return root if isinstance(root, Path) else Path(".wf_mcp_store")
|
||||||
|
```
|
||||||
|
|
||||||
|
Update `WfMcpService.__post_init__` to:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def __post_init__(self) -> None:
|
||||||
|
"""Install broker-local system specs when enabled.
|
||||||
|
|
||||||
|
Workflow stores are injected by entrypoint/config construction. This service
|
||||||
|
must not guess workflow persistence from the MCP catalog/auth store because
|
||||||
|
CLI, MCP, and future HTTP frontends may share or swap those stores.
|
||||||
|
"""
|
||||||
|
if self.include_builtin_specs:
|
||||||
|
for source in builtin_sources().values():
|
||||||
|
self.register_capability_source(source)
|
||||||
|
self.register_capability_source(admin_source())
|
||||||
|
```
|
||||||
|
|
||||||
|
If `Path` becomes unused in `core.py`, remove `from pathlib import Path`.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify Task 3**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_mcp\service\test_catalog.py::test_service_does_not_install_workflow_stores_implicitly -q
|
||||||
|
uv run ruff check src\wf_mcp\broker\service\core.py tests\wf_mcp\service\test_catalog.py
|
||||||
|
uv run ruff format --check src\wf_mcp\broker\service\core.py tests\wf_mcp\service\test_catalog.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: tests pass, lint pass, format pass.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Fix Direct Service Tests That Need Workflow Stores
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify only tests that fail after Task 3.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run targeted workflow API/service tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: if failures appear, they should be tests that constructed `WfMcpService(store=...)` but then used workflow artifact/draft/run operations.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Patch only failing tests by injecting stores explicitly**
|
||||||
|
|
||||||
|
For any failing direct `WfMcpService(...)` test that needs workflow stores, use this pattern:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from wf_api import file_workflow_stores
|
||||||
|
|
||||||
|
|
||||||
|
root = local_temp_root() / "test_specific_name"
|
||||||
|
workflow_stores = file_workflow_stores(root)
|
||||||
|
service = WfMcpService(
|
||||||
|
store=FileStore(root / "mcp"),
|
||||||
|
artifact_store=workflow_stores.artifact_store,
|
||||||
|
draft_workspace_store=workflow_stores.draft_workspace_store,
|
||||||
|
run_store=workflow_stores.run_store,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not add stores to tests that only exercise broker catalog/admin/source behavior.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Keep no-store behavior tests intact**
|
||||||
|
|
||||||
|
Tests like these should keep `artifact_store=None` through `WorkflowOperationContext` or direct service construction because they prove graceful no-store behavior:
|
||||||
|
|
||||||
|
```python
|
||||||
|
assert result["nodes"] == []
|
||||||
|
assert result["deployments"] == []
|
||||||
|
```
|
||||||
|
|
||||||
|
Do not “fix” these by injecting stores unless the test is explicitly about stored workflow data.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Verify targeted tests**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service -q
|
||||||
|
uv run ruff check tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service
|
||||||
|
uv run ruff format --check tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: targeted tests pass and no broad fixture churn.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Document the Store Ownership Rule
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `docs/superpowers/research/2026-06-01-wf-api-extraction-map.md`
|
||||||
|
- Modify: `docs/current_roadmap.md` if it already has a `wf_api` section
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update the extraction map**
|
||||||
|
|
||||||
|
In `docs/superpowers/research/2026-06-01-wf-api-extraction-map.md`, under `### Store Ownership Ambiguity`, replace the section with:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Store Ownership
|
||||||
|
|
||||||
|
- `wf_artifacts` owns workflow store protocols and file-backed implementations.
|
||||||
|
- `wf_api.stores.WorkflowStores` groups the artifact, draft workspace, and run stores as protocol-neutral API dependencies.
|
||||||
|
- MCP config construction creates file-backed workflow stores from `BrokerConfig.store_root` and injects them into `WfMcpService`.
|
||||||
|
- `WfMcpService.__post_init__` no longer creates workflow stores from its MCP `Store`; direct service tests must inject stores when they exercise workflow persistence.
|
||||||
|
- Future HTTP/API entrypoints should construct or receive the same `WorkflowStores` bundle instead of importing `wf_mcp`.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update roadmap only if there is a matching section**
|
||||||
|
|
||||||
|
If `docs/current_roadmap.md` has a `wf_api` or API extraction section, add:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- Workflow store ownership is explicit: entrypoints construct/inject `WorkflowStores`; `WfMcpService` no longer guesses stores from the MCP store root.
|
||||||
|
```
|
||||||
|
|
||||||
|
If there is no matching section, skip this file.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify docs are not stale**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n "_store_root\\(|creates default `FileWorkflowArtifactStore`|installs default.*store" src docs tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: no current docs/tests claim `WfMcpService` installs default workflow stores. Historical plans may still mention old implementation; leave historical plans alone unless they are current roadmap/research docs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Final Verification
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- All touched files.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run focused suite**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest tests\wf_api tests\wf_mcp\workflow_surface tests\wf_mcp\test_broker_server.py tests\wf_mcp\service -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all selected tests pass.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run full suite**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run pytest -q
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: full suite passes with the repo’s known skip/xfail counts.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run lint and format checks**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run ruff check src\wf_api src\wf_mcp tests\wf_api tests\wf_mcp
|
||||||
|
uv run ruff format --check src\wf_api src\wf_mcp tests\wf_api tests\wf_mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: all checks pass.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run typecheck**
|
||||||
|
|
||||||
|
Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run basedpyright --level error
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `0 errors, 0 warnings, 0 notes`. If the command exits nonzero only because of the known workspace enumeration warning, report that exactly.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Review
|
||||||
|
|
||||||
|
- Spec coverage: This plan covers explicit store creation, `WfMcpService.__post_init__` cleanup, config behavior preservation, test migration, and docs.
|
||||||
|
- Placeholder scan: No `TODO`/`TBD` placeholders remain. Historical-plan references are explicitly scoped.
|
||||||
|
- Type consistency: `WorkflowStores` uses protocol types from `wf_artifacts`; `file_workflow_stores()` returns file-backed implementations; `WfMcpService` field types do not change.
|
||||||
|
- Scope check: This does not implement FastAPI, persisted-run conflict handling, or store locking. Those are separate slices.
|
||||||
@@ -404,12 +404,13 @@ src/wf_cli/
|
|||||||
- `self.service._get_qualified_spec(qualified_name)` — used 3 times. This is a private method on `WfMcpService`. Must become a port method.
|
- `self.service._get_qualified_spec(qualified_name)` — used 3 times. This is a private method on `WfMcpService`. Must become a port method.
|
||||||
- `self.service._record_event(event)` — used 7 times. This is a private method. Must become a port method (or the event bus itself becomes a port).
|
- `self.service._record_event(event)` — used 7 times. This is a private method. Must become a port method (or the event bus itself becomes a port).
|
||||||
|
|
||||||
### Store Ownership Ambiguity
|
### Store Ownership
|
||||||
|
|
||||||
- `WfMcpService.__post_init__` creates default `FileWorkflowArtifactStore`, `FileDraftWorkspaceStore`, `FileRunStore` if not provided. These are protocol-neutral stores.
|
- `wf_artifacts` owns workflow store protocols and file-backed implementations.
|
||||||
- The stores are created from `_store_root(self.store)` which uses the MCP `Store` root.
|
- `wf_api.stores.WorkflowStores` groups the artifact, draft workspace, and run stores as protocol-neutral API dependencies.
|
||||||
- After extraction, store creation should be the caller's responsibility (config-driven), not `WfMcpService`'s.
|
- MCP config construction creates file-backed workflow stores from `BrokerConfig.store_root` and injects them into `WfMcpService`.
|
||||||
- Next extraction slice must define the store initialization boundary: which caller constructs `FileWorkflowArtifactStore`, `FileDraftWorkspaceStore`, and `FileRunStore`; how config/API callers inject protocol-neutral store implementations; and which tests must explicitly construct stores instead of relying on `WfMcpService.__post_init__`.
|
- `WfMcpService.__post_init__` no longer creates workflow stores from its MCP `Store`; direct service tests must inject stores when they exercise workflow persistence.
|
||||||
|
- Future HTTP/API entrypoints should construct or receive the same `WorkflowStores` bundle instead of importing `wf_mcp`.
|
||||||
|
|
||||||
### Naming Confusion
|
### Naming Confusion
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ from __future__ import annotations
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment
|
from wf_artifacts import (
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
WorkflowDeployment,
|
||||||
|
)
|
||||||
from wf_mcp.capabilities import DiscoveredPrompt, DiscoveredResource, DiscoveredTool
|
from wf_mcp.capabilities import DiscoveredPrompt, DiscoveredResource, DiscoveredTool
|
||||||
from wf_mcp.models import AuthRecord, ConnectionConfig
|
from wf_mcp.models import AuthRecord, ConnectionConfig
|
||||||
from wf_mcp.sdk import ToolCallResult
|
from wf_mcp.sdk import ToolCallResult
|
||||||
@@ -136,6 +141,8 @@ async def prepare_demo_service(root: Path) -> WfMcpService:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(root / "mcp_store"),
|
store=FileStore(root / "mcp_store"),
|
||||||
artifact_store=FileWorkflowArtifactStore(root / "artifacts"),
|
artifact_store=FileWorkflowArtifactStore(root / "artifacts"),
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(root / "mcp_store"),
|
||||||
|
run_store=FileRunStore(root / "mcp_store"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ from .operation_context import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .runtime_dependencies import RuntimeDependencies, resolve_runtime_dependencies
|
from .runtime_dependencies import RuntimeDependencies, resolve_runtime_dependencies
|
||||||
|
from .stores import WorkflowStores, file_workflow_stores
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"DEFAULT_CALL_STEP_ID",
|
"DEFAULT_CALL_STEP_ID",
|
||||||
@@ -78,4 +79,6 @@ __all__ = [
|
|||||||
"workflow_output_schema_for_authoring",
|
"workflow_output_schema_for_authoring",
|
||||||
"wrapper_hints_for_capability",
|
"wrapper_hints_for_capability",
|
||||||
"resolve_runtime_dependencies",
|
"resolve_runtime_dependencies",
|
||||||
|
"WorkflowStores",
|
||||||
|
"file_workflow_stores",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from wf_artifacts import (
|
||||||
|
DraftWorkspaceStore,
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
RunStore,
|
||||||
|
WorkflowArtifactStore,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True, slots=True)
|
||||||
|
class WorkflowStores:
|
||||||
|
"""Protocol-neutral persistence dependencies for workflow APIs."""
|
||||||
|
|
||||||
|
artifact_store: WorkflowArtifactStore
|
||||||
|
draft_workspace_store: DraftWorkspaceStore
|
||||||
|
run_store: RunStore
|
||||||
|
|
||||||
|
|
||||||
|
def file_workflow_stores(root: str | Path) -> WorkflowStores:
|
||||||
|
"""Create process-local file-backed workflow stores under one root."""
|
||||||
|
store_root = Path(root)
|
||||||
|
return WorkflowStores(
|
||||||
|
artifact_store=FileWorkflowArtifactStore(store_root),
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(store_root),
|
||||||
|
run_store=FileRunStore(store_root),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["WorkflowStores", "file_workflow_stores"]
|
||||||
@@ -3,11 +3,7 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from wf_artifacts import (
|
from wf_api import file_workflow_stores
|
||||||
FileDraftWorkspaceStore,
|
|
||||||
FileRunStore,
|
|
||||||
FileWorkflowArtifactStore,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ..control import BrokerConfigFile
|
from ..control import BrokerConfigFile
|
||||||
from ..models import BrokerConfig
|
from ..models import BrokerConfig
|
||||||
@@ -27,11 +23,12 @@ def load_broker_config(path: str | Path) -> BrokerConfig:
|
|||||||
def build_service_from_config(config: BrokerConfig) -> WfMcpService:
|
def build_service_from_config(config: BrokerConfig) -> WfMcpService:
|
||||||
"""Create a broker service with SDK adapters for configured connections."""
|
"""Create a broker service with SDK adapters for configured connections."""
|
||||||
runtime_factory = PersistentSessionFactory()
|
runtime_factory = PersistentSessionFactory()
|
||||||
|
workflow_stores = file_workflow_stores(config.store_root)
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(config.store_root),
|
store=FileStore(config.store_root),
|
||||||
artifact_store=FileWorkflowArtifactStore(config.store_root),
|
artifact_store=workflow_stores.artifact_store,
|
||||||
draft_workspace_store=FileDraftWorkspaceStore(config.store_root),
|
draft_workspace_store=workflow_stores.draft_workspace_store,
|
||||||
run_store=FileRunStore(config.store_root),
|
run_store=workflow_stores.run_store,
|
||||||
# Discovery can use short-lived SDK sessions. Workflow execution needs
|
# Discovery can use short-lived SDK sessions. Workflow execution needs
|
||||||
# a persistent runtime so stateful MCP servers keep session/page state
|
# a persistent runtime so stateful MCP servers keep session/page state
|
||||||
# across sequential workflow nodes.
|
# across sequential workflow nodes.
|
||||||
|
|||||||
@@ -2,16 +2,12 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from wf_artifacts import (
|
from wf_artifacts import (
|
||||||
DraftWorkspaceStore,
|
DraftWorkspaceStore,
|
||||||
FileDraftWorkspaceStore,
|
|
||||||
FileRunStore,
|
|
||||||
FileWorkflowArtifactStore,
|
|
||||||
RunStore,
|
RunStore,
|
||||||
WorkflowArtifact,
|
WorkflowArtifact,
|
||||||
WorkflowArtifactCatalogEntry,
|
WorkflowArtifactCatalogEntry,
|
||||||
@@ -68,12 +64,6 @@ from .builtins import builtin_sources
|
|||||||
from .specs import get_qualified_spec, qualify_spec
|
from .specs import get_qualified_spec, qualify_spec
|
||||||
|
|
||||||
|
|
||||||
def _store_root(store: Store) -> Path:
|
|
||||||
"""Return the file root for stores that expose one, else use local default."""
|
|
||||||
root = getattr(store, "root", None)
|
|
||||||
return root if isinstance(root, Path) else Path(".wf_mcp_store")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class WfMcpService:
|
class WfMcpService:
|
||||||
store: Store
|
store: Store
|
||||||
@@ -89,15 +79,12 @@ class WfMcpService:
|
|||||||
tool_executor: ToolExecutor | None = None
|
tool_executor: ToolExecutor | None = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
"""Install broker-local system specs when enabled."""
|
"""Install broker-local system specs when enabled.
|
||||||
if self.artifact_store is None:
|
|
||||||
self.artifact_store = FileWorkflowArtifactStore(_store_root(self.store))
|
Workflow stores are injected by entrypoint/config construction. This service
|
||||||
if self.draft_workspace_store is None:
|
must not guess workflow persistence from the MCP catalog/auth store because
|
||||||
self.draft_workspace_store = FileDraftWorkspaceStore(
|
CLI, MCP, and future HTTP frontends may share or swap those stores.
|
||||||
_store_root(self.store)
|
"""
|
||||||
)
|
|
||||||
if self.run_store is None:
|
|
||||||
self.run_store = FileRunStore(_store_root(self.store))
|
|
||||||
if self.include_builtin_specs:
|
if self.include_builtin_specs:
|
||||||
for source in builtin_sources().values():
|
for source in builtin_sources().values():
|
||||||
self.register_capability_source(source)
|
self.register_capability_source(source)
|
||||||
|
|||||||
@@ -6,7 +6,12 @@ import asyncio
|
|||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from wf_artifacts import FileWorkflowArtifactStore, RequiredCapability, WorkflowArtifact
|
from wf_artifacts import (
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
RequiredCapability,
|
||||||
|
WorkflowArtifact,
|
||||||
|
)
|
||||||
from wf_api.artifacts import WorkflowArtifactApi
|
from wf_api.artifacts import WorkflowArtifactApi
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
from wf_mcp.models import ConnectionConfig
|
from wf_mcp.models import ConnectionConfig
|
||||||
@@ -111,11 +116,11 @@ def _artifact_api(
|
|||||||
*,
|
*,
|
||||||
register_echo: bool = False,
|
register_echo: bool = False,
|
||||||
) -> tuple[WorkflowArtifactApi, WfMcpService]:
|
) -> tuple[WorkflowArtifactApi, WfMcpService]:
|
||||||
|
mcp_root = artifact_store.root / "artifacts_mcp" / str(id(artifact_store))
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(
|
store=FileStore(mcp_root),
|
||||||
artifact_store.root / "artifacts_mcp" / str(id(artifact_store))
|
|
||||||
),
|
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
|
||||||
)
|
)
|
||||||
if register_echo:
|
if register_echo:
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
@@ -255,9 +260,11 @@ def test_handler_delegation_for_inspect_artifact() -> None:
|
|||||||
artifact_store = FileWorkflowArtifactStore(
|
artifact_store = FileWorkflowArtifactStore(
|
||||||
local_temp_root() / "artifacts_delegation"
|
local_temp_root() / "artifacts_delegation"
|
||||||
)
|
)
|
||||||
|
mcp_root = artifact_store.root / "delegation_mcp"
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(artifact_store.root / "delegation_mcp"),
|
store=FileStore(mcp_root),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
|
||||||
)
|
)
|
||||||
artifact_store.save_artifact(_echo_artifact())
|
artifact_store.save_artifact(_echo_artifact())
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import asyncio
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from wf_artifacts import FileWorkflowArtifactStore
|
from wf_artifacts import FileWorkflowArtifactStore, FileDraftWorkspaceStore
|
||||||
from wf_api.capabilities import WorkflowCapabilityApi
|
from wf_api.capabilities import WorkflowCapabilityApi
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
from wf_mcp.models import ConnectionConfig
|
from wf_mcp.models import ConnectionConfig
|
||||||
@@ -22,9 +22,11 @@ def _capability_api(
|
|||||||
register_echo: bool = False,
|
register_echo: bool = False,
|
||||||
register_failing: bool = False,
|
register_failing: bool = False,
|
||||||
) -> tuple[WorkflowCapabilityApi, WfMcpService]:
|
) -> tuple[WorkflowCapabilityApi, WfMcpService]:
|
||||||
|
mcp_root = artifact_store.root / "caps_mcp" / str(id(artifact_store))
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(artifact_store.root / "caps_mcp" / str(id(artifact_store))),
|
store=FileStore(mcp_root),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
|
||||||
)
|
)
|
||||||
if register_echo:
|
if register_echo:
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
@@ -241,9 +243,11 @@ def test_create_draft_workspace_from_capability() -> None:
|
|||||||
def test_handler_delegates_to_capability_api() -> None:
|
def test_handler_delegates_to_capability_api() -> None:
|
||||||
"""WorkflowSurfaceHandlers methods produce the same result as direct API."""
|
"""WorkflowSurfaceHandlers methods produce the same result as direct API."""
|
||||||
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "cap_api_delegation")
|
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "cap_api_delegation")
|
||||||
|
mcp_root = artifact_store.root / "delegation_mcp"
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(artifact_store.root / "delegation_mcp"),
|
store=FileStore(mcp_root),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from wf_artifacts import FileWorkflowArtifactStore
|
from wf_artifacts import FileWorkflowArtifactStore, FileDraftWorkspaceStore
|
||||||
from wf_api.drafts import WorkflowDraftApi
|
from wf_api.drafts import WorkflowDraftApi
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
from wf_mcp.models import ConnectionConfig
|
from wf_mcp.models import ConnectionConfig
|
||||||
@@ -55,9 +55,11 @@ def _draft_api(
|
|||||||
*,
|
*,
|
||||||
register_echo: bool = False,
|
register_echo: bool = False,
|
||||||
) -> tuple[WorkflowDraftApi, WfMcpService]:
|
) -> tuple[WorkflowDraftApi, WfMcpService]:
|
||||||
|
mcp_root = artifact_store.root / "drafts_mcp" / str(id(artifact_store))
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(artifact_store.root / "drafts_mcp" / str(id(artifact_store))),
|
store=FileStore(mcp_root),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
|
||||||
)
|
)
|
||||||
if register_echo:
|
if register_echo:
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
@@ -326,9 +328,11 @@ def test_delegation_smoke_validate_draft_equivalence() -> None:
|
|||||||
artifact_store = FileWorkflowArtifactStore(
|
artifact_store = FileWorkflowArtifactStore(
|
||||||
local_temp_root() / "drafts_delegation_smoke"
|
local_temp_root() / "drafts_delegation_smoke"
|
||||||
)
|
)
|
||||||
|
mcp_root = artifact_store.root / "delegation_mcp"
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(artifact_store.root / "delegation_mcp"),
|
store=FileStore(mcp_root),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment
|
from wf_artifacts import FileWorkflowArtifactStore, FileRunStore, WorkflowDeployment
|
||||||
from wf_api.runs import WorkflowRunApi
|
from wf_api.runs import WorkflowRunApi
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
from wf_mcp.broker.service.workflow_operation_context import context_from_service
|
from wf_mcp.broker.service.workflow_operation_context import context_from_service
|
||||||
@@ -43,6 +43,7 @@ def _service_with_echo(
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(root / "mcp"),
|
store=FileStore(root / "mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(root / "mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -67,6 +68,7 @@ def _service_with_failing(
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(root / "mcp"),
|
store=FileStore(root / "mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(root / "mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from wf_api.stores import WorkflowStores, file_workflow_stores
|
||||||
|
from wf_artifacts import (
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
)
|
||||||
|
|
||||||
|
from tests.wf_mcp.test_support import local_temp_root
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_workflow_stores_constructs_all_three_file_stores() -> None:
|
||||||
|
root = local_temp_root() / "wf_api_file_workflow_stores"
|
||||||
|
|
||||||
|
stores = file_workflow_stores(root)
|
||||||
|
|
||||||
|
assert isinstance(stores, WorkflowStores)
|
||||||
|
assert isinstance(stores.artifact_store, FileWorkflowArtifactStore)
|
||||||
|
assert isinstance(stores.draft_workspace_store, FileDraftWorkspaceStore)
|
||||||
|
assert isinstance(stores.run_store, FileRunStore)
|
||||||
|
assert stores.artifact_store.root == root
|
||||||
|
assert stores.draft_workspace_store.root == root
|
||||||
|
assert stores.run_store.root == root
|
||||||
|
|
||||||
|
|
||||||
|
def test_wf_api_exports_workflow_stores() -> None:
|
||||||
|
from wf_api import WorkflowStores as ExportedWorkflowStores
|
||||||
|
from wf_api import file_workflow_stores as exported_file_workflow_stores
|
||||||
|
|
||||||
|
assert ExportedWorkflowStores is WorkflowStores
|
||||||
|
assert exported_file_workflow_stores is file_workflow_stores
|
||||||
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|||||||
import asyncio
|
import asyncio
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
from wf_artifacts import FileDraftWorkspaceStore
|
|
||||||
from wf_authoring import NodeSpec
|
from wf_authoring import NodeSpec
|
||||||
from wf_core import RunStatus
|
from wf_core import RunStatus
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
@@ -65,12 +64,13 @@ def test_service_installs_builtin_stdlib_specs_by_default() -> None:
|
|||||||
assert "wf.mcp" not in service.capability_sources
|
assert "wf.mcp" not in service.capability_sources
|
||||||
|
|
||||||
|
|
||||||
def test_service_installs_default_draft_workspace_store() -> None:
|
def test_service_does_not_install_workflow_stores_implicitly() -> None:
|
||||||
root = local_temp_root() / "service_default_draft_workspace_store"
|
root = local_temp_root() / "service_no_implicit_workflow_stores"
|
||||||
service = WfMcpService(store=FileStore(root))
|
service = WfMcpService(store=FileStore(root))
|
||||||
|
|
||||||
assert isinstance(service.draft_workspace_store, FileDraftWorkspaceStore)
|
assert service.artifact_store is None
|
||||||
assert service.draft_workspace_store.root == root
|
assert service.draft_workspace_store is None
|
||||||
|
assert service.run_store is None
|
||||||
|
|
||||||
|
|
||||||
def test_service_registers_empty_source_for_connection_without_catalog() -> None:
|
def test_service_registers_empty_source_for_connection_without_catalog() -> None:
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import json
|
|||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
from wf_artifacts import (
|
from wf_artifacts import (
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
FileWorkflowArtifactStore,
|
FileWorkflowArtifactStore,
|
||||||
RequiredCapability,
|
RequiredCapability,
|
||||||
WorkflowArtifact,
|
WorkflowArtifact,
|
||||||
@@ -352,6 +354,7 @@ def test_broker_runs_non_interrupting_workflow_deployment() -> None:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "broker_run_mcp_store"),
|
store=FileStore(local_temp_root() / "broker_run_mcp_store"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "broker_run_mcp_store"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -431,6 +434,7 @@ def test_broker_run_deployment_pauses_and_resumes_interrupting_artifacts() -> No
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "broker_run_interrupt_mcp_store"),
|
store=FileStore(local_temp_root() / "broker_run_interrupt_mcp_store"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "broker_run_interrupt_mcp_store"),
|
||||||
)
|
)
|
||||||
server = create_broker_server(service)
|
server = create_broker_server(service)
|
||||||
|
|
||||||
@@ -467,17 +471,18 @@ def test_broker_run_deployment_pauses_and_resumes_interrupting_artifacts() -> No
|
|||||||
assert resumed["resume_readiness"] == "not_applicable"
|
assert resumed["resume_readiness"] == "not_applicable"
|
||||||
|
|
||||||
|
|
||||||
def test_build_service_from_config_uses_store_root_for_artifacts() -> None:
|
def test_build_service_from_config_uses_store_root_for_workflow_stores() -> None:
|
||||||
store_root = local_temp_root() / "broker_config_artifact_store"
|
store_root = local_temp_root() / "broker_config_workflow_stores"
|
||||||
service = build_service_from_config(
|
config = BrokerConfig(store_root=store_root, connections=[])
|
||||||
BrokerConfig(
|
|
||||||
store_root=store_root,
|
service = build_service_from_config(config)
|
||||||
connections=[],
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert isinstance(service.artifact_store, FileWorkflowArtifactStore)
|
assert isinstance(service.artifact_store, FileWorkflowArtifactStore)
|
||||||
|
assert isinstance(service.draft_workspace_store, FileDraftWorkspaceStore)
|
||||||
|
assert isinstance(service.run_store, FileRunStore)
|
||||||
assert service.artifact_store.root == store_root
|
assert service.artifact_store.root == store_root
|
||||||
|
assert service.draft_workspace_store.root == store_root
|
||||||
|
assert service.run_store.root == store_root
|
||||||
|
|
||||||
|
|
||||||
def _artifact() -> WorkflowArtifact:
|
def _artifact() -> WorkflowArtifact:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import asyncio
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from wf_artifacts import (
|
from wf_artifacts import (
|
||||||
|
FileRunStore,
|
||||||
FileWorkflowArtifactStore,
|
FileWorkflowArtifactStore,
|
||||||
RequiredCapability,
|
RequiredCapability,
|
||||||
ResumeReadiness,
|
ResumeReadiness,
|
||||||
@@ -381,9 +382,11 @@ def _deployment(*, bindings: dict[str, str] | None = None) -> WorkflowDeployment
|
|||||||
|
|
||||||
|
|
||||||
def _handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers:
|
def _handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers:
|
||||||
|
mcp_root = local_temp_root() / f"{artifact_store.root.name}_mcp"
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / f"{artifact_store.root.name}_mcp"),
|
store=FileStore(mcp_root),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(mcp_root),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ from typing import Any
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
from wf_artifacts import (
|
from wf_artifacts import (
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
FileRunStore,
|
||||||
FileWorkflowArtifactStore,
|
FileWorkflowArtifactStore,
|
||||||
RequiredCapability,
|
RequiredCapability,
|
||||||
WorkflowArtifact,
|
WorkflowArtifact,
|
||||||
@@ -159,9 +161,12 @@ def multiply(current: int | None, incoming: int) -> int:
|
|||||||
|
|
||||||
|
|
||||||
def handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers:
|
def handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers:
|
||||||
|
mcp_root = artifact_store.root / "surface_mcp" / str(id(artifact_store))
|
||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(artifact_store.root / "surface_mcp" / str(id(artifact_store))),
|
store=FileStore(mcp_root),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(mcp_root),
|
||||||
|
run_store=FileRunStore(mcp_root),
|
||||||
)
|
)
|
||||||
return WorkflowSurfaceHandlers(service)
|
return WorkflowSurfaceHandlers(service)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from wf_artifacts import FileWorkflowArtifactStore
|
from wf_artifacts import FileWorkflowArtifactStore, FileDraftWorkspaceStore
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
from wf_mcp.models import ConnectionConfig
|
from wf_mcp.models import ConnectionConfig
|
||||||
from wf_mcp.storage import FileStore
|
from wf_mcp.storage import FileStore
|
||||||
@@ -102,6 +102,9 @@ def test_workflow_surface_does_not_auto_map_raw_mcp_content_blocks() -> None:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_content_only_content_hint_mcp"),
|
store=FileStore(local_temp_root() / "surface_content_only_content_hint_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_content_only_content_hint_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment
|
from wf_artifacts import (
|
||||||
|
FileWorkflowArtifactStore,
|
||||||
|
FileDraftWorkspaceStore,
|
||||||
|
WorkflowDeployment,
|
||||||
|
)
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
from wf_mcp.models import ConnectionConfig
|
from wf_mcp.models import ConnectionConfig
|
||||||
from wf_mcp.storage import FileStore
|
from wf_mcp.storage import FileStore
|
||||||
@@ -122,6 +126,9 @@ def test_workflow_surface_validates_draft_workspace_with_live_outcomes() -> None
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_workspace_validate_mcp"),
|
store=FileStore(local_temp_root() / "surface_workspace_validate_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_workspace_validate_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -153,6 +160,9 @@ def test_workflow_surface_creates_minimal_draft_workspace_with_error_route() ->
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_minimal_workspace_mcp"),
|
store=FileStore(local_temp_root() / "surface_minimal_workspace_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_minimal_workspace_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -201,6 +211,9 @@ def test_workflow_surface_minimal_draft_honors_explicit_error_message_source() -
|
|||||||
artifact_store=FileWorkflowArtifactStore(
|
artifact_store=FileWorkflowArtifactStore(
|
||||||
local_temp_root() / "surface_minimal_explicit_error"
|
local_temp_root() / "surface_minimal_explicit_error"
|
||||||
),
|
),
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_minimal_explicit_error_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -259,6 +272,9 @@ def test_workflow_surface_accepts_canonical_bindings_for_minimal_workspace() ->
|
|||||||
artifact_store=FileWorkflowArtifactStore(
|
artifact_store=FileWorkflowArtifactStore(
|
||||||
local_temp_root() / "surface_minimal_canonical"
|
local_temp_root() / "surface_minimal_canonical"
|
||||||
),
|
),
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_minimal_canonical_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
h = WorkflowSurfaceHandlers(service)
|
h = WorkflowSurfaceHandlers(service)
|
||||||
|
|
||||||
@@ -309,6 +325,9 @@ def test_workflow_surface_creates_draft_workspace_from_capability_hints() -> Non
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_workspace_from_capability_mcp"),
|
store=FileStore(local_temp_root() / "surface_workspace_from_capability_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_workspace_from_capability_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -362,6 +381,9 @@ def test_workflow_surface_creates_artifact_from_workspace() -> None:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_workspace_artifact_mcp"),
|
store=FileStore(local_temp_root() / "surface_workspace_artifact_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_workspace_artifact_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -404,6 +426,9 @@ def test_workflow_surface_workspace_artifact_infers_raw_concrete_dependency() ->
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_workspace_artifact_raw_mcp"),
|
store=FileStore(local_temp_root() / "surface_workspace_artifact_raw_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_workspace_artifact_raw_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -441,6 +466,9 @@ def test_workflow_surface_creates_wrapper_from_workspace() -> None:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_workspace_wrapper_mcp"),
|
store=FileStore(local_temp_root() / "surface_workspace_wrapper_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_workspace_wrapper_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -478,6 +506,9 @@ def test_workflow_surface_low_confidence_draft_returns_patch_guidance() -> None:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_workspace_low_confidence_mcp"),
|
store=FileStore(local_temp_root() / "surface_workspace_low_confidence_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
draft_workspace_store=FileDraftWorkspaceStore(
|
||||||
|
local_temp_root() / "surface_workspace_low_confidence_mcp"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(
|
ConnectionConfig(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment
|
from wf_artifacts import FileWorkflowArtifactStore, FileRunStore, WorkflowDeployment
|
||||||
from wf_mcp.broker import WfMcpService
|
from wf_mcp.broker import WfMcpService
|
||||||
from wf_mcp.models import ConnectionConfig
|
from wf_mcp.models import ConnectionConfig
|
||||||
from wf_mcp.storage import FileStore
|
from wf_mcp.storage import FileStore
|
||||||
@@ -51,6 +51,7 @@ def test_workflow_surface_runs_non_interrupting_deployment() -> None:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_run_mcp"),
|
store=FileStore(local_temp_root() / "surface_run_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "surface_run_mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -112,6 +113,7 @@ def test_workflow_surface_failed_deployment_exposes_error_on_run_and_inspect() -
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_failed_run_error_mcp"),
|
store=FileStore(local_temp_root() / "surface_failed_run_error_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "surface_failed_run_error_mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -153,6 +155,7 @@ def test_workflow_surface_run_deployment_can_include_trace_detail() -> None:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_run_trace_detail_mcp"),
|
store=FileStore(local_temp_root() / "surface_run_trace_detail_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "surface_run_trace_detail_mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -201,6 +204,7 @@ def test_workflow_surface_run_deployment_can_read_empty_trace_range() -> None:
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_run_trace_empty_range_mcp"),
|
store=FileStore(local_temp_root() / "surface_run_trace_empty_range_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "surface_run_trace_empty_range_mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -237,6 +241,7 @@ def test_workflow_surface_runs_deployment_with_bound_node_spec_dependency() -> N
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_bound_node_mcp"),
|
store=FileStore(local_temp_root() / "surface_bound_node_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "surface_bound_node_mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -263,6 +268,7 @@ def test_workflow_surface_runs_artifact_created_from_concrete_node_ref() -> None
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_created_bound_node_mcp"),
|
store=FileStore(local_temp_root() / "surface_created_bound_node_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "surface_created_bound_node_mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
@@ -390,6 +396,7 @@ def test_workflow_surface_runs_deployment_with_bound_reducer_dependency() -> Non
|
|||||||
service = WfMcpService(
|
service = WfMcpService(
|
||||||
store=FileStore(local_temp_root() / "surface_reducer_mcp"),
|
store=FileStore(local_temp_root() / "surface_reducer_mcp"),
|
||||||
artifact_store=artifact_store,
|
artifact_store=artifact_store,
|
||||||
|
run_store=FileRunStore(local_temp_root() / "surface_reducer_mcp"),
|
||||||
)
|
)
|
||||||
service.register_connection(
|
service.register_connection(
|
||||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||||
|
|||||||
Reference in New Issue
Block a user