fmt + docs

This commit is contained in:
lda
2026-06-02 02:01:48 +07:00 Verified
parent 5f8c4224f0
commit cf5290168e
9 changed files with 705 additions and 23 deletions
@@ -355,12 +355,30 @@ Move workflow capability operations last:
list_capabilities list_capabilities
inspect_capability inspect_capability
call_capability call_capability
create_draft_workspace_from_capability
``` ```
Reason: capabilities look simple but are the messiest boundary. They combine Reason: capabilities look simple but are the messiest boundary. They combine
planner-visible source inventory, wrapper artifacts, direct wrapper calls, planner-visible source inventory, wrapper artifacts, direct wrapper calls,
external live source calls, source visibility, and schema/wrapper hints. Keep external live source calls, source visibility, and schema/wrapper hints.
them in the MCP-backed implementation until the other domain services are stable. `create_draft_workspace_from_capability` also belongs here because it is driven
by `inspect_capability` wrapper hints. Keep them in the MCP-backed
implementation until the other domain services are stable.
#### After Slice 4E: Helper Promotion Cleanup
Once the handler is mostly a compatibility adapter, promote duplicated helper
symbols into stable homes instead of leaving long-term cross-domain private
imports:
```text
raw_plan_from_artifact -> wf_api.artifact_plans or wf_artifacts
artifact_capability_id -> wf_api artifact/capability refs helper
available_sources_from_capability_sources -> wf_api source snapshot helper
```
This should be a cleanup slice, not part of 4E unless required to avoid circular
imports or behavior drift.
### If/Then ### If/Then
@@ -0,0 +1,656 @@
# wf_api Slice 4E: Capabilities 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:** Move workflow capability discovery, inspection, direct capability calls, wrapper capability projection, and capability-backed draft bootstrap out of `WorkflowSurfaceHandlers` into `wf_api.capabilities.WorkflowCapabilityApi`.
**Architecture:** `WorkflowCapabilityApi` depends on `WorkflowOperationContext`, `WorkflowDraftApi`, and existing `wf_api` helper modules. It must not depend on `WfMcpService`, MCP events, MCP tools, or MCP request models. `WorkflowSurfaceHandlers` becomes a thin adapter/delegator for workflow operations.
**Tech Stack:** Python 3.14+, `wf_api.operation_context`, `wf_api.drafts`, `wf_api.runs`, `wf_api.wrapper_hints`, `wf_api.refs`, `wf_artifacts`, `wf_authoring`, `wf_core.RuntimeContext`, pytest, ruff, basedpyright.
---
## Scope
### Move In This Slice
Move these methods from `WorkflowSurfaceHandlers`:
```text
list_capabilities
inspect_capability
call_capability
create_draft_workspace_from_capability
```
Move these wrapper/capability private methods:
```text
_wrapper_artifact_for_capability_name
_wrapper_capability_summaries
_wrapper_capability_detail
_call_wrapper_artifact
```
Move or duplicate only the helpers needed by those methods:
```text
_schema_field_names
_source_id_for_capability
_artifact_capability_id
_raw_plan_from_artifact
_required_capability_payloads
_draft_name_from_capability
```
### Do Not Move In This Slice
Do not move MCP tool registration, MCP request/response Pydantic models, proxy/admin/broker runtime code, or CLI code.
### Invariants
- No public payload changes.
- No MCP tool schema changes.
- `WorkflowSurfaceHandlers` public capability method signatures stay unchanged.
- `wf_api` imports no `wf_mcp`.
- Direct raw NodeSpec calls still use `build_async_registry`.
- Direct wrapper calls still reject unsupported interrupting wrappers through `direct_wrapper_interrupt_diagnostic`.
- Full saved workflows still run through deployments, not direct capability calls.
- `create_draft_workspace_from_capability` keeps using inspect-capability wrapper hints.
---
## Design Notes
Capability extraction is the final domain split because it spans several concepts:
- live planner-visible source NodeSpecs
- saved wrapper artifacts projected as workflow-facing capabilities
- direct capability REPL calls
- wrapper calls through workflow execution
- wrapper-hint-driven draft bootstrap
Keep this in one `WorkflowCapabilityApi` for now. Do not create five tiny services unless tests prove the file is too large after extraction.
Temporary private helper duplication is allowed. A later cleanup can promote common helpers such as `artifact_capability_id`, `raw_plan_from_artifact`, and source snapshots into better shared modules. Do not widen this slice just to make helper names perfect.
---
## Task 1: Create `wf_api.capabilities`
**Files:**
- Create: `src/wf_api/capabilities.py`
- Modify: `src/wf_api/__init__.py`
- Test: `tests/wf_api/test_capability_api.py`
- [ ] **Step 1: Create service skeleton**
Create `src/wf_api/capabilities.py`:
```python
from __future__ import annotations
from collections.abc import Sequence
from typing import Any
from wf_artifacts import (
DependencyDiagnostic,
DiagnosticSeverity,
WorkflowArtifact,
WorkflowCapabilityRef,
)
from wf_authoring import build_async_registry
from wf_core import RuntimeContext
from wf_core.models.steps import InputBinding, OutputBinding
from wf_core.paths import GraphSourcePath
from wf_platform import CapabilitySource, page_items
from .drafts import WorkflowDraftApi
from .models import RawWorkflowPlan
from .operation_context import WorkflowOperationContext
from .refs import parse_workflow_surface_capability_id
from .saved_subgraphs import direct_wrapper_interrupt_diagnostic
from .wrapper_hints import (
workflow_output_schema_for_authoring,
wrapper_hints_for_capability,
)
class WorkflowCapabilityApi:
"""Workflow-facing capability discovery, inspection, and REPL calls.
This service owns the source/wrapper projection, while adapter-specific MCP
tool schemas stay outside wf_api.
"""
def __init__(self, context: WorkflowOperationContext) -> None:
self.context = context
self.drafts = WorkflowDraftApi(context)
```
- [ ] **Step 2: Add local list helpers**
Add local helpers rather than importing `wf_mcp.shared`:
```python
def _matches_query(*values: object, query: str | None) -> bool:
if query is None:
return True
needle = query.strip().casefold()
if not needle:
return True
return any(needle in str(value).casefold() for value in values if value is not None)
def _paged_list_payload(
key: str,
items: Sequence[dict[str, Any]],
*,
cursor: str | None,
limit: int,
) -> dict[str, Any]:
page = page_items(items, cursor=cursor, limit=limit)
return {key: list(page.items), "next_cursor": page.next_cursor, "total": page.total}
```
This duplicates current list behavior without importing MCP shared helpers into
`wf_api`.
- [ ] **Step 3: Export capability service**
In `src/wf_api/__init__.py`:
```python
from .capabilities import WorkflowCapabilityApi
```
Add `"WorkflowCapabilityApi"` to `__all__`.
---
## Task 2: Move Discovery And Inspection
**Files:**
- Modify: `src/wf_api/capabilities.py`
- [ ] **Step 1: Move `list_capabilities`**
Move the existing handler body into:
```python
async def list_capabilities(
self,
*,
query: str | None = None,
source_id: str | None = None,
cursor: str | None = None,
limit: int = 50,
) -> dict[str, Any]:
...
```
Required replacements:
```python
self.service.capability_sources -> self.context.capability_sources
self._wrapper_capability_summaries(...) -> self._wrapper_capability_summaries(...)
matches_query(...) -> _matches_query(...)
paged_list_payload(...) -> _paged_list_payload(...)
```
Preserve sorting and response shape.
- [ ] **Step 2: Move `inspect_capability`**
Move the existing handler body into:
```python
async def inspect_capability(self, *, qualified_name: str) -> dict[str, Any]:
...
```
Required replacements:
```python
self.service.capability_sources -> self.context.capability_sources
self._wrapper_capability_detail(...) -> self._wrapper_capability_detail(...)
```
Preserve:
- enabled/planner visibility filtering
- wrapper detail fallback
- `KeyError(f"unknown workflow capability {qualified_name!r}")`
- `wrapper_hints` payload
- [ ] **Step 3: Add helper functions**
Move or duplicate:
```text
_schema_field_names
_artifact_capability_id
_required_capability_payloads
```
Do not import private helpers from `wf_api.artifacts` or `wf_api.runs` in this slice unless the import is already public. Private duplication is acceptable here.
---
## Task 3: Move Wrapper Capability Projection
**Files:**
- Modify: `src/wf_api/capabilities.py`
- [ ] **Step 1: Add artifact store helper**
Add:
```python
def _artifact_store(self):
return self.context.artifact_store
```
Do not raise from this helper. Existing wrapper projection returns no wrapper
rows/details when artifact store is absent.
- [ ] **Step 2: Move `_wrapper_artifact_for_capability_name`**
Move the existing method, replacing:
```python
self.service.artifact_store -> self.context.artifact_store
```
Preserve current behavior:
- invalid capability ids return `None`
- non-wrapper artifacts return `None`
- missing artifact store returns `None`
- missing artifact id/version returns `None`
- [ ] **Step 3: Move `_wrapper_capability_summaries`**
Move the method and replace:
```python
matches_query(...) -> _matches_query(...)
```
Preserve `source_id not in {None, "workflow"}` filtering and the existing row shape.
- [ ] **Step 4: Move `_wrapper_capability_detail`**
Move the method unchanged except helper references now point to local functions.
Preserve:
- `kind == "wrapper_artifact"`
- `required_capabilities`
- `wrapper_hints`
- output/input schema fields
---
## Task 4: Move Direct Capability Calls
**Files:**
- Modify: `src/wf_api/capabilities.py`
- [ ] **Step 1: Move `call_capability`**
Move the existing handler body into:
```python
async def call_capability(
self,
*,
qualified_name: str,
payload: dict[str, Any],
deployment_id: str | None = None,
) -> dict[str, Any]:
...
```
Required replacements:
```python
self.service._get_qualified_spec(qualified_name) -> self.context.specs.get_qualified_spec(qualified_name)
self.service.capability_sources -> self.context.capability_sources
self._call_wrapper_artifact(...) -> self._call_wrapper_artifact(...)
```
Preserve direct NodeSpec call behavior:
- build handler with `build_async_registry(spec)[spec.name]`
- pass `RuntimeContext(current_node_id=spec.name)`
- catch `Exception` and return `capability_call_failed` diagnostic payload
- successful response returns `kind: "node_spec"` and empty diagnostics
- [ ] **Step 2: Move `_call_wrapper_artifact`**
Move the wrapper call method into `WorkflowCapabilityApi`.
Required replacements:
```python
self.service.artifact_store -> self.context.artifact_store
self.service.run_workflow_from_plan(...) -> self.context.runtime.run_workflow_from_plan(...)
```
Call runtime with the same deployment/artifact arguments:
```python
run = await self.context.runtime.run_workflow_from_plan(
plan,
payload,
deployment=deployment,
artifact=artifact,
)
```
Preserve:
- `direct_wrapper_interrupt_diagnostic` rejection
- deployment target validation
- `kind: "wrapper_artifact"`
- `outcome: run.status.value`
- `output: run.output`
- [ ] **Step 3: Move raw plan helper**
Move or duplicate:
```text
_raw_plan_from_artifact
_plan_field
```
Do not import private `_raw_plan_from_artifact` from `wf_api.runs` unless you
first make it public. Keeping a local copy is acceptable for this slice.
---
## Task 5: Move Capability-Backed Draft Bootstrap
**Files:**
- Modify: `src/wf_api/capabilities.py`
- [ ] **Step 1: Move `create_draft_workspace_from_capability`**
Move the existing handler body into `WorkflowCapabilityApi`.
Required replacements:
```python
capability = await self.inspect_capability(...)
result = await self.drafts.create_minimal_draft_workspace(...)
```
Preserve:
- wrapper-hint defaults
- explicit `input` overrides `input_map`
- explicit `output` overrides `output_map`
- returned `wrapper_hints`
- returned `next_actions`
- [ ] **Step 2: Move `_draft_name_from_capability`**
Move or duplicate:
```python
def _draft_name_from_capability(capability_name: str) -> str:
"""Return a stable draft name when caller does not provide one."""
return capability_name.replace(".", "_").replace("-", "_")
```
---
## Task 6: Wire `WorkflowSurfaceHandlers`
**Files:**
- Modify: `src/wf_mcp/workflow_surface/handlers.py`
- [ ] **Step 1: Add import and instance**
Add:
```python
from wf_api.capabilities import WorkflowCapabilityApi
```
In `WorkflowSurfaceHandlers.__init__`, reuse the existing operation context:
```python
context = context_from_service(service)
self._capabilities = WorkflowCapabilityApi(context)
self._drafts = WorkflowDraftApi(context)
self._artifacts = WorkflowArtifactApi(context)
self._deployments = WorkflowDeploymentApi(context)
self._runs = WorkflowRunApi(context)
```
- [ ] **Step 2: Delegate moved methods**
Replace method bodies for:
```text
list_capabilities
inspect_capability
call_capability
create_draft_workspace_from_capability
```
Example:
```python
async def inspect_capability(self, *, qualified_name: str) -> dict[str, Any]:
"""Return one planner-visible workflow capability contract."""
return await self._capabilities.inspect_capability(qualified_name=qualified_name)
```
- [ ] **Step 3: Remove moved private methods**
Remove these from `handlers.py` after delegation:
```text
_wrapper_artifact_for_capability_name
_wrapper_capability_summaries
_wrapper_capability_detail
_call_wrapper_artifact
```
Then run:
```powershell
rg -n "_schema_field_names|_source_id_for_capability|_artifact_capability_id|_raw_plan_from_artifact|_plan_field|_draft_name_from_capability|_required_capability_payloads" src/wf_mcp/workflow_surface/handlers.py
```
Remove each helper only if it has no remaining handler caller. The target after
4E should be close to zero private workflow-domain helpers in `handlers.py`.
- [ ] **Step 4: Prune imports**
Use `ruff check` to remove unused imports. Likely candidates:
```text
DependencyDiagnostic
DiagnosticSeverity
WorkflowArtifact
WorkflowCapabilityRef
CapabilitySource
build_async_registry
RuntimeContext
direct_wrapper_interrupt_diagnostic
workflow_output_schema_for_authoring
wrapper_hints_for_capability
parse_workflow_surface_capability_id
matches_query
paged_list_payload
```
Do not remove imports still needed by method signatures such as `InputBinding`,
`OutputBinding`, `GraphSourcePath`, `TraceRange`, or `RawWorkflowPlan`.
---
## Task 7: Add Focused Capability API Tests
**Files:**
- Create: `tests/wf_api/test_capability_api.py`
- [ ] **Step 1: Cover live source capability listing and inspection**
Build a service with `echo_tool`, adapt with `context_from_service`, instantiate
`WorkflowCapabilityApi`, and assert:
```python
listed = asyncio.run(api.list_capabilities())
assert listed["total"] >= 1
assert any(item["name"] == "demo.personal.echo_tool" for item in listed["capabilities"])
detail = asyncio.run(api.inspect_capability(qualified_name="demo.personal.echo_tool"))
assert detail["name"] == "demo.personal.echo_tool"
assert "wrapper_hints" in detail
```
- [ ] **Step 2: Cover direct NodeSpec call**
Call:
```python
result = asyncio.run(
api.call_capability(
qualified_name="demo.personal.echo_tool",
payload={"text": "hello"},
)
)
```
Assert stable fields:
```python
assert result["kind"] == "node_spec"
assert result["outcome"] == "ok"
assert result["diagnostics"] == []
```
- [ ] **Step 3: Cover saved wrapper projection**
Save a wrapper artifact and assert:
- `list_capabilities(source_id="workflow")` includes `kind == "wrapper_artifact"`
- `inspect_capability(qualified_name="workflow.<id>.v<version>")` returns wrapper detail
- `call_capability(...)` executes the wrapper through runtime and returns `kind == "wrapper_artifact"`
Use existing artifact helpers where possible. Do not duplicate entire run tests.
- [ ] **Step 4: Cover capability-backed draft bootstrap**
Call `create_draft_workspace_from_capability(...)` and assert:
```python
assert result["workspace_id"] == "echo_ws"
assert result["revision"] == 1
assert "wrapper_hints" in result
assert "next_actions" in result
```
Fetch the workspace through `WorkflowDraftApi` and assert the draft uses the
expected capability name.
- [ ] **Step 5: Cover handler delegation smoke**
Compare stable fields from handler and direct API for one method:
```python
handler_result = asyncio.run(handlers.inspect_capability(qualified_name=name))
api_result = asyncio.run(api.inspect_capability(qualified_name=name))
assert handler_result["name"] == api_result["name"]
assert handler_result["kind"] == api_result["kind"]
```
Do not duplicate every capability behavior test in both layers.
---
## Task 8: Verification
- [ ] **Step 1: Run focused capability tests**
```powershell
uv run pytest tests/wf_api/test_capability_api.py tests/wf_mcp/workflow_surface/test_capabilities.py -q
```
If `tests/wf_mcp/workflow_surface/test_capabilities.py` does not exist, run the
closest existing workflow-surface capability tests discovered by `rg -n "call_capability|inspect_capability|list_capabilities" tests/wf_mcp`.
Expected: pass.
- [ ] **Step 2: Run adjacent API tests**
```powershell
uv run pytest tests/wf_api/test_drafts_service.py tests/wf_api/test_artifact_api.py tests/wf_api/test_deployment_api.py tests/wf_api/test_run_api.py -q
```
Expected: pass.
- [ ] **Step 3: Run import-direction test**
```powershell
uv run pytest tests/wf_api/test_import_direction.py -q
```
Expected: pass; `wf_api` has no `wf_mcp` imports.
- [ ] **Step 4: Run ruff on touched files**
```powershell
uv run ruff check src/wf_api/capabilities.py src/wf_api/__init__.py src/wf_mcp/workflow_surface/handlers.py tests/wf_api/test_capability_api.py
```
Expected: all checks pass.
- [ ] **Step 5: Run basedpyright on touched files**
```powershell
uv run basedpyright --level error src/wf_api/capabilities.py src/wf_mcp/workflow_surface/handlers.py tests/wf_api/test_capability_api.py
```
Expected: `0 errors`.
- [ ] **Step 6: Optional full suite**
```powershell
uv run pytest -q
```
Expected: full suite passes with the projects existing skipped/xfailed counts.
---
## Self-Review Checklist
- `wf_api.capabilities` imports no `wf_mcp`.
- `WorkflowSurfaceHandlers` public capability signatures are unchanged.
- `create_draft_workspace_from_capability` moved with capability inspection.
- Direct NodeSpec calls still work.
- Saved wrapper discovery and direct wrapper calls still work.
- Full saved workflows still require deployments.
- No public payload shape changed.
- No MCP schema changed.
- Handler is now mostly a thin compatibility adapter over `wf_api` domain services.
## Follow-Up Cleanup After 4E
After this slice lands, consider a cleanup plan to promote shared helpers:
```text
wf_api.runs._raw_plan_from_artifact -> wf_api.artifact_plans.raw_plan_from_artifact
wf_api.capabilities._artifact_capability_id -> wf_api.artifact_refs.artifact_capability_id
wf_api.deployments._available_sources -> wf_api.source_snapshots.available_sources_from_capability_sources
```
Do not do that cleanup inside 4E unless it is required to remove circular imports
or duplicate behavior bugs.
+2 -2
View File
@@ -42,8 +42,8 @@ class WorkflowDeploymentApi:
} }
async def inspect_deployment(self, *, deployment_id: str) -> dict[str, Any]: async def inspect_deployment(self, *, deployment_id: str) -> dict[str, Any]:
return self._artifact_store().get_deployment(deployment_id).model_dump( return (
mode="json" self._artifact_store().get_deployment(deployment_id).model_dump(mode="json")
) )
async def save_deployment(self, deployment: dict[str, Any]) -> dict[str, Any]: async def save_deployment(self, deployment: dict[str, Any]) -> dict[str, Any]:
+2 -2
View File
@@ -57,8 +57,8 @@ class WorkflowRunApi:
workflow_input: dict[str, Any], workflow_input: dict[str, Any],
trace_range: TraceRangeLike | None = None, trace_range: TraceRangeLike | None = None,
) -> dict[str, Any]: ) -> dict[str, Any]:
deployment, artifact, diagnostics, tree = self.deployments.deployment_validation( deployment, artifact, diagnostics, tree = (
deployment_id self.deployments.deployment_validation(deployment_id)
) )
if diagnostics: if diagnostics:
return _run_payload( return _run_payload(
@@ -18,7 +18,12 @@ import httpx
from mcp.client.streamable_http import StreamableHTTPError from mcp.client.streamable_http import StreamableHTTPError
from mcp.shared.exceptions import McpError from mcp.shared.exceptions import McpError
from wf_artifacts import DependencyDiagnostic, DiagnosticSeverity, WorkflowArtifact, WorkflowDeployment from wf_artifacts import (
DependencyDiagnostic,
DiagnosticSeverity,
WorkflowArtifact,
WorkflowDeployment,
)
from .adapters import require_adapter from .adapters import require_adapter
from .core import WfMcpService from .core import WfMcpService
+1
View File
@@ -765,6 +765,7 @@ class WorkflowSurfaceHandlers:
trace_range=trace_range, trace_range=trace_range,
) )
def _required_capability_payloads( def _required_capability_payloads(
requirements: dict[str, RequiredCapability], requirements: dict[str, RequiredCapability],
) -> dict[str, dict[str, Any]]: ) -> dict[str, dict[str, Any]]:
+4 -4
View File
@@ -112,7 +112,9 @@ def _artifact_api(
register_echo: bool = False, register_echo: bool = False,
) -> tuple[WorkflowArtifactApi, WfMcpService]: ) -> tuple[WorkflowArtifactApi, WfMcpService]:
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "artifacts_mcp" / str(id(artifact_store))), store=FileStore(
artifact_store.root / "artifacts_mcp" / str(id(artifact_store))
),
artifact_store=artifact_store, artifact_store=artifact_store,
) )
if register_echo: if register_echo:
@@ -236,9 +238,7 @@ def test_create_wrapper_from_workspace_saves_kind_wrapper() -> None:
def test_inspect_artifact_returns_stable_fields() -> None: def test_inspect_artifact_returns_stable_fields() -> None:
artifact_store = FileWorkflowArtifactStore( artifact_store = FileWorkflowArtifactStore(local_temp_root() / "artifacts_inspect")
local_temp_root() / "artifacts_inspect"
)
api, _service = _artifact_api(artifact_store) api, _service = _artifact_api(artifact_store)
artifact_store.save_artifact(_echo_artifact()) artifact_store.save_artifact(_echo_artifact())
+9 -11
View File
@@ -83,7 +83,9 @@ def _deployment_api(
register_echo: bool = False, register_echo: bool = False,
) -> tuple[WorkflowDeploymentApi, WfMcpService]: ) -> tuple[WorkflowDeploymentApi, WfMcpService]:
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "deployments_mcp" / str(id(artifact_store))), store=FileStore(
artifact_store.root / "deployments_mcp" / str(id(artifact_store))
),
artifact_store=artifact_store, artifact_store=artifact_store,
) )
if register_echo: if register_echo:
@@ -105,7 +107,9 @@ def test_save_deployment_stores_and_returns_stable_fields() -> None:
id="echo.personal", id="echo.personal",
artifact_id="echo", artifact_id="echo",
artifact_version=1, artifact_version=1,
bindings=[{"logical_source": "demo", "concrete_source": "demo.personal"}], bindings=[
{"logical_source": "demo", "concrete_source": "demo.personal"}
],
).model_dump(mode="json") ).model_dump(mode="json")
) )
) )
@@ -227,9 +231,7 @@ def test_validate_deployment_live_check_calls_live_checker() -> None:
def test_handler_delegation_for_validate_deployment() -> None: def test_handler_delegation_for_validate_deployment() -> None:
"""WorkflowSurfaceHandlers.validate_deployment delegates to WorkflowDeploymentApi.""" """WorkflowSurfaceHandlers.validate_deployment delegates to WorkflowDeploymentApi."""
artifact_store = FileWorkflowArtifactStore( artifact_store = FileWorkflowArtifactStore(local_temp_root() / "deploy_delegation")
local_temp_root() / "deploy_delegation"
)
service = WfMcpService( service = WfMcpService(
store=FileStore(artifact_store.root / "delegation_mcp"), store=FileStore(artifact_store.root / "delegation_mcp"),
artifact_store=artifact_store, artifact_store=artifact_store,
@@ -248,12 +250,8 @@ def test_handler_delegation_for_validate_deployment() -> None:
context = context_from_service(service) context = context_from_service(service)
api = WorkflowDeploymentApi(context) api = WorkflowDeploymentApi(context)
handler_result = asyncio.run( handler_result = asyncio.run(h.validate_deployment(deployment_id="echo.personal"))
h.validate_deployment(deployment_id="echo.personal") api_result = asyncio.run(api.validate_deployment(deployment_id="echo.personal"))
)
api_result = asyncio.run(
api.validate_deployment(deployment_id="echo.personal")
)
assert handler_result["status"] == api_result["status"] assert handler_result["status"] == api_result["status"]
assert len(handler_result["diagnostics"]) == len(api_result["diagnostics"]) assert len(handler_result["diagnostics"]) == len(api_result["diagnostics"])
+5 -1
View File
@@ -12,7 +12,11 @@ from wf_mcp.storage import FileStore
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
from tests.wf_mcp.test_support import echo_tool, local_temp_root from tests.wf_mcp.test_support import echo_tool, local_temp_root
from tests.wf_mcp.workflow_surface.conftest import echo_artifact, failing_artifact, failing_tool from tests.wf_mcp.workflow_surface.conftest import (
echo_artifact,
failing_artifact,
failing_tool,
)
class SimpleTraceRange: class SimpleTraceRange: