ts for some reason, moved to wf_api
This commit is contained in:
@@ -0,0 +1,204 @@
|
|||||||
|
# Move RawWorkflowPlan to wf_api.models
|
||||||
|
|
||||||
|
> **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:** Extract `RawWorkflowPlan` from `wf_mcp.models` to canonical `wf_api.models`, keeping a compatibility shim in `wf_mcp.models`.
|
||||||
|
|
||||||
|
**Architecture:** `RawWorkflowPlan` is a standalone Pydantic model with no dependencies on other `wf_mcp.models` definitions. It depends only on `pydantic` and `wf_core` (Edge, InputBinding, Step). This makes it safe to move without entanglement. The `wf_mcp.models` module will re-export from `wf_api.models` as a shim.
|
||||||
|
|
||||||
|
**Tech Stack:** Python 3.14, Pydantic v2, pytest, ruff, basedpyright
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Create `src/wf_api/models.py` with RawWorkflowPlan
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `src/wf_api/models.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the file with the model**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from wf_core import Edge
|
||||||
|
from wf_core.models.steps import InputBinding, Step
|
||||||
|
|
||||||
|
|
||||||
|
class RawWorkflowPlan(BaseModel):
|
||||||
|
"""Raw authoring plan using the same graph step and edge models as core."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
input_schema: dict[str, Any]
|
||||||
|
state_schema: dict[str, Any]
|
||||||
|
output_schema: dict[str, Any]
|
||||||
|
outcomes: list[str] = Field(
|
||||||
|
default_factory=lambda: ["ok"],
|
||||||
|
description=(
|
||||||
|
"Declared public workflow outcomes. Legacy plans without this field "
|
||||||
|
"default to ok."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
output: list[InputBinding] = Field(
|
||||||
|
default_factory=list,
|
||||||
|
description=(
|
||||||
|
"Optional root workflow output bindings. Sources read graph paths "
|
||||||
|
"such as state.result and targets write the public output payload."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
start: str
|
||||||
|
nodes: list[Step]
|
||||||
|
edges: list[Edge]
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify the file was created correctly**
|
||||||
|
|
||||||
|
Run: `python -c "from wf_api.models import RawWorkflowPlan; print(RawWorkflowPlan.__name__)"`
|
||||||
|
Expected: `RawWorkflowPlan`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Replace `wf_mcp.models.RawWorkflowPlan` definition with shim
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `src/wf_mcp/models.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace the RawWorkflowPlan class definition with a re-export**
|
||||||
|
|
||||||
|
Replace the `RawWorkflowPlan` class block (lines 45-68) with:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# RawWorkflowPlan moved to wf_api.models; re-exported here for backward compat.
|
||||||
|
from wf_api.models import RawWorkflowPlan # noqa: F401
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep the original import block for pydantic, Edge, InputBinding, Step — they are still used indirectly via the re-export. Actually, after removing the class definition, `BaseModel`, `Field`, `Edge`, `InputBinding`, `Step` are no longer needed by this file. Remove those imports if no other class in the file uses them.
|
||||||
|
|
||||||
|
Check: The remaining classes in `wf_mcp/models.py` are `ConnectionConfig`, `AuthRecord`, `CatalogSnapshot`, `BrokerConfig`, `dump_catalog_snapshot`. These use `dataclass`, `field`, `Path`, `Any`, `CatalogNodeEntry`, `CatalogPromptEntry`, `CatalogResourceEntry`. They do NOT use `BaseModel`, `Field`, `Edge`, `InputBinding`, `Step`.
|
||||||
|
|
||||||
|
So remove: `from pydantic import BaseModel, Field`, `from wf_core import Edge`, `from wf_core.models.steps import InputBinding, Step`.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run ruff on the file**
|
||||||
|
|
||||||
|
Run: `uv run ruff check src/wf_mcp/models.py`
|
||||||
|
Expected: no errors
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run basedpyright on the file**
|
||||||
|
|
||||||
|
Run: `uv run basedpyright --level error src/wf_mcp/models.py`
|
||||||
|
Expected: no errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Update `wf_mcp/__init__.py` to import from shim
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Verify: `src/wf_mcp/__init__.py`
|
||||||
|
|
||||||
|
No change needed — `wf_mcp/__init__.py` already imports `RawWorkflowPlan` from `.models`, and the shim re-exports it. Verify this still works.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Verify the import chain works**
|
||||||
|
|
||||||
|
Run: `python -c "from wf_mcp import RawWorkflowPlan; print(RawWorkflowPlan.__name__)"`
|
||||||
|
Expected: `RawWorkflowPlan`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: Add focused tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `tests/wf_api/test_raw_workflow_plan_extraction.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write the tests**
|
||||||
|
|
||||||
|
```python
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
def test_canonical_import_from_wf_api_models() -> None:
|
||||||
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
|
||||||
|
assert RawWorkflowPlan.__name__ == "RawWorkflowPlan"
|
||||||
|
|
||||||
|
|
||||||
|
def test_compat_import_from_wf_mcp_models() -> None:
|
||||||
|
from wf_mcp.models import RawWorkflowPlan as CompatPlan
|
||||||
|
|
||||||
|
assert CompatPlan.__name__ == "RawWorkflowPlan"
|
||||||
|
|
||||||
|
|
||||||
|
def test_canonical_and_compat_are_identical() -> None:
|
||||||
|
from wf_api.models import RawWorkflowPlan as Canonical
|
||||||
|
from wf_mcp.models import RawWorkflowPlan as Compat
|
||||||
|
|
||||||
|
assert Canonical is Compat
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: The import direction rule is already covered by `tests/wf_api/test_import_direction.py::test_wf_api_has_no_wf_mcp_imports`. No need to duplicate.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the new tests**
|
||||||
|
|
||||||
|
Run: `uv run pytest tests/wf_api/test_raw_workflow_plan_extraction.py -v`
|
||||||
|
Expected: all 3 PASS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: Update test imports to use canonical path
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `tests/wf_mcp/service/conftest.py`
|
||||||
|
- Modify: `tests/wf_mcp/workflow_surface/test_runs.py`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Update `tests/wf_mcp/service/conftest.py`**
|
||||||
|
|
||||||
|
Change line 8 from:
|
||||||
|
```python
|
||||||
|
from wf_mcp.models import AuthRecord, ConnectionConfig, RawWorkflowPlan
|
||||||
|
```
|
||||||
|
to:
|
||||||
|
```python
|
||||||
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
from wf_mcp.models import AuthRecord, ConnectionConfig
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Update `tests/wf_mcp/workflow_surface/test_runs.py`**
|
||||||
|
|
||||||
|
Change line 32 from:
|
||||||
|
```python
|
||||||
|
from wf_mcp.models import RawWorkflowPlan
|
||||||
|
```
|
||||||
|
to:
|
||||||
|
```python
|
||||||
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run ruff on touched files**
|
||||||
|
|
||||||
|
Run: `uv run ruff check tests/wf_mcp/service/conftest.py tests/wf_mcp/workflow_surface/test_runs.py`
|
||||||
|
Expected: no errors
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run basedpyright on touched files**
|
||||||
|
|
||||||
|
Run: `uv run basedpyright --level error tests/wf_mcp/service/conftest.py tests/wf_mcp/workflow_surface/test_runs.py`
|
||||||
|
Expected: no errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Run full test suite and verify
|
||||||
|
|
||||||
|
- [ ] **Step 1: Run pytest**
|
||||||
|
|
||||||
|
Run: `uv run pytest -q`
|
||||||
|
Expected: all tests pass
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run ruff on all touched files**
|
||||||
|
|
||||||
|
Run: `uv run ruff check src/wf_api/models.py src/wf_mcp/models.py src/wf_mcp/__init__.py tests/wf_api/test_raw_workflow_plan_extraction.py tests/wf_mcp/service/conftest.py tests/wf_mcp/workflow_surface/test_runs.py`
|
||||||
|
Expected: no errors
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run basedpyright on touched files**
|
||||||
|
|
||||||
|
Run: `uv run basedpyright --level error src/wf_api/models.py src/wf_mcp/models.py`
|
||||||
|
Expected: no errors
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
from wf_core import Edge
|
||||||
|
from wf_core.models.steps import InputBinding, Step
|
||||||
|
|
||||||
|
|
||||||
|
class RawWorkflowPlan(BaseModel):
|
||||||
|
"""Raw authoring plan using the same graph step and edge models as core."""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
input_schema: dict[str, Any]
|
||||||
|
state_schema: dict[str, Any]
|
||||||
|
output_schema: dict[str, Any]
|
||||||
|
outcomes: list[str] = Field(
|
||||||
|
default_factory=lambda: ["ok"],
|
||||||
|
description=(
|
||||||
|
"Declared public workflow outcomes. Legacy plans without this field "
|
||||||
|
"default to ok."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
output: list[InputBinding] = Field(
|
||||||
|
default_factory=list,
|
||||||
|
description=(
|
||||||
|
"Optional root workflow output bindings. Sources read graph paths "
|
||||||
|
"such as state.result and targets write the public output payload."
|
||||||
|
),
|
||||||
|
)
|
||||||
|
start: str
|
||||||
|
nodes: list[Step]
|
||||||
|
edges: list[Edge]
|
||||||
@@ -4,8 +4,9 @@ from typing import Any
|
|||||||
|
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
|
||||||
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
|
||||||
from ..workflow_surface import WorkflowSurfaceHandlers
|
from ..workflow_surface import WorkflowSurfaceHandlers
|
||||||
from ..models import RawWorkflowPlan
|
|
||||||
from .service import WfMcpService
|
from .service import WfMcpService
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ from wf_core import (
|
|||||||
execute_workflow_result_async,
|
execute_workflow_result_async,
|
||||||
resume_workflow_result_async,
|
resume_workflow_result_async,
|
||||||
)
|
)
|
||||||
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
from wf_api.runtime_dependencies import resolve_runtime_dependencies
|
||||||
from wf_platform import (
|
from wf_platform import (
|
||||||
CapabilityBuckets,
|
CapabilityBuckets,
|
||||||
CapabilitySource,
|
CapabilitySource,
|
||||||
@@ -47,7 +48,6 @@ from ...models import (
|
|||||||
CatalogSnapshot,
|
CatalogSnapshot,
|
||||||
BrokerConfig,
|
BrokerConfig,
|
||||||
ConnectionConfig,
|
ConnectionConfig,
|
||||||
RawWorkflowPlan,
|
|
||||||
)
|
)
|
||||||
from ...sdk import BackendAdapter
|
from ...sdk import BackendAdapter
|
||||||
from ...runtime import ToolExecutor
|
from ...runtime import ToolExecutor
|
||||||
@@ -55,7 +55,6 @@ from ...shared.errors import error_payload
|
|||||||
from ...shared.names import RESERVED_CONNECTION_IDS
|
from ...shared.names import RESERVED_CONNECTION_IDS
|
||||||
from ...storage import Store
|
from ...storage import Store
|
||||||
from ...workflow.wrappers import _model_from_schema
|
from ...workflow.wrappers import _model_from_schema
|
||||||
from wf_api.runtime_dependencies import resolve_runtime_dependencies
|
|
||||||
from ...workflow_surface.saved_subgraphs import (
|
from ...workflow_surface.saved_subgraphs import (
|
||||||
SavedSubgraphTree,
|
SavedSubgraphTree,
|
||||||
prepare_saved_subgraphs,
|
prepare_saved_subgraphs,
|
||||||
|
|||||||
+3
-30
@@ -4,12 +4,11 @@ from dataclasses import asdict, dataclass, field
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
from wf_core import Edge
|
|
||||||
from wf_core.models.steps import InputBinding, Step
|
|
||||||
|
|
||||||
from .capabilities import CatalogNodeEntry, CatalogPromptEntry, CatalogResourceEntry
|
from .capabilities import CatalogNodeEntry, CatalogPromptEntry, CatalogResourceEntry
|
||||||
|
|
||||||
|
# RawWorkflowPlan moved to wf_api.models; re-exported here for backward compat.
|
||||||
|
from wf_api.models import RawWorkflowPlan # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class ConnectionConfig:
|
class ConnectionConfig:
|
||||||
@@ -42,32 +41,6 @@ class CatalogSnapshot:
|
|||||||
return age_ms > self.max_age_seconds * 1000
|
return age_ms > self.max_age_seconds * 1000
|
||||||
|
|
||||||
|
|
||||||
class RawWorkflowPlan(BaseModel):
|
|
||||||
"""Raw authoring plan using the same graph step and edge models as core."""
|
|
||||||
|
|
||||||
name: str
|
|
||||||
input_schema: dict[str, Any]
|
|
||||||
state_schema: dict[str, Any]
|
|
||||||
output_schema: dict[str, Any]
|
|
||||||
outcomes: list[str] = Field(
|
|
||||||
default_factory=lambda: ["ok"],
|
|
||||||
description=(
|
|
||||||
"Declared public workflow outcomes. Legacy plans without this field "
|
|
||||||
"default to ok."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
output: list[InputBinding] = Field(
|
|
||||||
default_factory=list,
|
|
||||||
description=(
|
|
||||||
"Optional root workflow output bindings. Sources read graph paths "
|
|
||||||
"such as state.result and targets write the public output payload."
|
|
||||||
),
|
|
||||||
)
|
|
||||||
start: str
|
|
||||||
nodes: list[Step]
|
|
||||||
edges: list[Edge]
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class BrokerConfig:
|
class BrokerConfig:
|
||||||
store_root: Path
|
store_root: Path
|
||||||
|
|||||||
@@ -54,14 +54,14 @@ from wf_api.constants import (
|
|||||||
DEFAULT_OK_OUTCOME,
|
DEFAULT_OK_OUTCOME,
|
||||||
RUNTIME_ERROR_CAPABILITY,
|
RUNTIME_ERROR_CAPABILITY,
|
||||||
)
|
)
|
||||||
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
from wf_api.next_actions import NextActions
|
||||||
from wf_api.refs import parse_workflow_surface_capability_id
|
from wf_api.refs import parse_workflow_surface_capability_id
|
||||||
|
|
||||||
from ..broker.service.adapters import require_adapter
|
from ..broker.service.adapters import require_adapter
|
||||||
from ..events import make_event
|
from ..events import make_event
|
||||||
from ..models import RawWorkflowPlan
|
|
||||||
from ..shared import matches_query, paged_list_payload
|
from ..shared import matches_query, paged_list_payload
|
||||||
from .models import TraceRange
|
from .models import TraceRange
|
||||||
from wf_api.next_actions import NextActions
|
|
||||||
from .saved_subgraphs import (
|
from .saved_subgraphs import (
|
||||||
SavedSubgraphTree,
|
SavedSubgraphTree,
|
||||||
direct_wrapper_interrupt_diagnostic,
|
direct_wrapper_interrupt_diagnostic,
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ from wf_core.models.steps import Step
|
|||||||
from wf_core.models.workflow_refs import WorkflowRef
|
from wf_core.models.workflow_refs import WorkflowRef
|
||||||
from wf_platform import CapabilitySource
|
from wf_platform import CapabilitySource
|
||||||
|
|
||||||
from ..models import RawWorkflowPlan
|
|
||||||
from wf_api.runtime_dependencies import resolve_runtime_dependencies
|
from wf_api.runtime_dependencies import resolve_runtime_dependencies
|
||||||
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
|
||||||
_STEPS_ADAPTER = TypeAdapter(list[Step])
|
_STEPS_ADAPTER = TypeAdapter(list[Step])
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
|
||||||
|
def test_canonical_import_from_wf_api_models() -> None:
|
||||||
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
|
||||||
|
assert RawWorkflowPlan.__name__ == "RawWorkflowPlan"
|
||||||
|
|
||||||
|
|
||||||
|
def test_compat_import_from_wf_mcp_models() -> None:
|
||||||
|
from wf_mcp.models import RawWorkflowPlan as CompatPlan
|
||||||
|
|
||||||
|
assert CompatPlan.__name__ == "RawWorkflowPlan"
|
||||||
|
|
||||||
|
|
||||||
|
def test_canonical_and_compat_are_identical() -> None:
|
||||||
|
from wf_api.models import RawWorkflowPlan as Canonical
|
||||||
|
from wf_mcp.models import RawWorkflowPlan as Compat
|
||||||
|
|
||||||
|
assert Canonical is Compat
|
||||||
@@ -5,7 +5,8 @@ from typing import Any
|
|||||||
from wf_authoring import node
|
from wf_authoring import node
|
||||||
from wf_core import END
|
from wf_core import END
|
||||||
from wf_mcp.capabilities import DiscoveredTool
|
from wf_mcp.capabilities import DiscoveredTool
|
||||||
from wf_mcp.models import AuthRecord, ConnectionConfig, RawWorkflowPlan
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
from wf_mcp.models import AuthRecord, ConnectionConfig
|
||||||
from wf_mcp.sdk import ToolCallResult
|
from wf_mcp.sdk import ToolCallResult
|
||||||
|
|
||||||
from ..test_support import (
|
from ..test_support import (
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ from .conftest import (
|
|||||||
|
|
||||||
|
|
||||||
def test_raw_workflow_plan_uses_core_step_and_edge_models() -> None:
|
def test_raw_workflow_plan_uses_core_step_and_edge_models() -> None:
|
||||||
from wf_mcp.models import RawWorkflowPlan
|
from wf_api.models import RawWorkflowPlan
|
||||||
|
|
||||||
plan = RawWorkflowPlan.model_validate(echo_artifact().plan)
|
plan = RawWorkflowPlan.model_validate(echo_artifact().plan)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user