Files
lda-wf/docs/historical/superpowers/plans/2026-06-02-wf-api-remove-double-delegation.md
T

762 lines
23 KiB
Markdown

# wf_api Remove Double Delegation 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:** Collapse workflow calls from `WorkflowApi -> WfMcpWorkflowApiBackend -> WorkflowSurfaceHandlers -> domain services` to `WorkflowApi -> domain services`.
**Architecture:** `WorkflowApi` becomes the protocol-neutral application facade that composes `WorkflowCapabilityApi`, `WorkflowDraftApi`, `WorkflowArtifactApi`, `WorkflowDeploymentApi`, and `WorkflowRunApi` from a `WorkflowOperationContext`. MCP and CLI construct `WorkflowApi(context_from_service(service))` directly. `WorkflowSurfaceHandlers` remains only as a temporary compatibility shim for legacy imports/tests, and `WfMcpWorkflowApiBackend` / `WorkflowApiBackend` are removed.
**Tech Stack:** Python 3.14, `wf_api`, `wf_mcp`, dataclasses, pytest, ruff, basedpyright.
---
## Current Chain
```text
wf_mcp tools / wf_cli
-> WorkflowApi
-> WfMcpWorkflowApiBackend
-> WorkflowSurfaceHandlers
-> WorkflowCapabilityApi / WorkflowDraftApi / WorkflowArtifactApi / WorkflowDeploymentApi / WorkflowRunApi
```
This creates two mechanical delegation layers. Adding one workflow operation currently requires touching at least `WorkflowApi`, `WorkflowApiBackend`, `WfMcpWorkflowApiBackend`, and usually `WorkflowSurfaceHandlers`.
## Target Chain
```text
wf_mcp tools / wf_cli
-> WorkflowApi
-> WorkflowCapabilityApi / WorkflowDraftApi / WorkflowArtifactApi / WorkflowDeploymentApi / WorkflowRunApi
```
Legacy imports of `WorkflowSurfaceHandlers` may still work, but only as a thin wrapper around `WorkflowApi`.
## Files
- Modify: `src/wf_api/models.py`
- Modify: `src/wf_api/__init__.py`
- Modify: `src/wf_api/service.py`
- Delete: `src/wf_api/backend.py`
- Delete: `src/wf_mcp/broker/service/workflow_api_backend.py`
- Modify: `src/wf_cli/context.py`
- Modify: `src/wf_cli/commands/runs.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Modify: `src/wf_mcp/workflow_surface/handlers.py`
- Modify: `tests/wf_cli/test_context.py`
- Modify: `tests/wf_api/test_cli_context_uses_api.py`
- Add: `tests/wf_api/test_direct_service.py`
- Add: `tests/wf_api/test_no_double_delegation.py`
- Modify docs: `docs/current_roadmap.md`, `docs/wf_mcp_architecture.md`, `docs/superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md`
---
### Task 1: Move TraceRange Out of `backend.py`
**Files:**
- Modify: `src/wf_api/models.py`
- Modify: `src/wf_api/__init__.py`
- Modify: `src/wf_cli/commands/runs.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- [ ] **Step 1: Write import regression test**
Add to `tests/wf_api/test_raw_workflow_plan_extraction.py`:
```python
def test_trace_range_exports_from_wf_api_models() -> None:
from wf_api import TraceRange
from wf_api.models import TraceRange as CanonicalTraceRange
assert TraceRange is CanonicalTraceRange
assert TraceRange(start=1, limit=2).start == 1
assert TraceRange(start=1, limit=2).limit == 2
```
- [ ] **Step 2: Run failing test**
Run:
```bash
uv run pytest tests\wf_api\test_raw_workflow_plan_extraction.py::test_trace_range_exports_from_wf_api_models -q
```
Expected: fail because `wf_api.models.TraceRange` does not exist yet.
- [ ] **Step 3: Add `TraceRange` to `wf_api.models`**
In `src/wf_api/models.py`, add imports:
```python
from dataclasses import dataclass
```
Then add before `RawWorkflowPlan`:
```python
@dataclass(frozen=True, slots=True)
class TraceRange:
"""Caller-bounded debug trace slice for durable deployment runs."""
start: int = 0
limit: int = 25
```
- [ ] **Step 4: Update `wf_api.__init__` export**
Change:
```python
from .backend import TraceRange, WorkflowApiBackend
```
to:
```python
from .models import RawWorkflowPlan, TraceRange
```
Remove `"WorkflowApiBackend"` from `__all__`. Keep `"TraceRange"`.
If `RawWorkflowPlan` was not exported before, include it only if already expected by tests; do not add new public API unless the existing file already imports it elsewhere.
- [ ] **Step 5: Update imports that referenced `wf_api.backend.TraceRange`**
In `src/wf_cli/commands/runs.py`, replace:
```python
from wf_api.backend import TraceRange
```
with:
```python
from wf_api import TraceRange
```
In `src/wf_mcp/workflow_surface/tools.py`, remove:
```python
from wf_api.backend import TraceRange as ApiTraceRange
```
Also remove `_to_api_trace_range()`. Later tasks pass MCP `TraceRange` directly because `WorkflowRunApi` validates trace ranges structurally through `TraceRangeLike`.
- [ ] **Step 6: Verify Task 1**
Run:
```bash
uv run pytest tests\wf_api\test_raw_workflow_plan_extraction.py::test_trace_range_exports_from_wf_api_models tests\wf_cli\test_run_deploy.py tests\wf_mcp\server\test_tools.py -q
uv run ruff check src\wf_api\models.py src\wf_api\__init__.py src\wf_cli\commands\runs.py src\wf_mcp\workflow_surface\tools.py tests\wf_api\test_raw_workflow_plan_extraction.py
uv run ruff format --check src\wf_api\models.py src\wf_api\__init__.py src\wf_cli\commands\runs.py src\wf_mcp\workflow_surface\tools.py tests\wf_api\test_raw_workflow_plan_extraction.py
```
Expected: tests pass, lint pass, format pass.
---
### Task 2: Make `WorkflowApi` Compose Domain Services Directly
**Files:**
- Modify: `src/wf_api/service.py`
- Add: `tests/wf_api/test_direct_service.py`
- [ ] **Step 1: Write direct-composition tests**
Create `tests/wf_api/test_direct_service.py`:
```python
from __future__ import annotations
import asyncio
from wf_artifacts import FileWorkflowArtifactStore
from wf_api import WorkflowApi
from wf_api.artifacts import WorkflowArtifactApi
from wf_api.capabilities import WorkflowCapabilityApi
from wf_api.deployments import WorkflowDeploymentApi
from wf_api.drafts import WorkflowDraftApi
from wf_api.runs import WorkflowRunApi
from wf_mcp.broker import WfMcpService
from wf_mcp.broker.service.workflow_operation_context import context_from_service
from wf_mcp.models import ConnectionConfig
from wf_mcp.storage import FileStore
from tests.wf_mcp.test_support import echo_tool, local_temp_root
def _api() -> WorkflowApi:
root = local_temp_root() / "wf_api_direct_composition"
service = WfMcpService(
store=FileStore(root / "mcp"),
artifact_store=FileWorkflowArtifactStore(root),
)
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_specs("demo.personal", echo_tool)
return WorkflowApi(context_from_service(service))
def test_workflow_api_composes_domain_services() -> None:
api = _api()
assert isinstance(api.capabilities, WorkflowCapabilityApi)
assert isinstance(api.drafts, WorkflowDraftApi)
assert isinstance(api.artifacts, WorkflowArtifactApi)
assert isinstance(api.deployments, WorkflowDeploymentApi)
assert isinstance(api.runs, WorkflowRunApi)
assert not hasattr(api, "backend")
def test_workflow_api_direct_capability_call() -> None:
api = _api()
result = asyncio.run(
api.call_capability(
qualified_name="demo.personal.echo_tool",
payload={"text": "hello"},
)
)
assert result["kind"] == "node_spec"
assert result["outcome"] == "ok"
assert result["output"] == {"echoed": "hello"}
```
- [ ] **Step 2: Run failing tests**
Run:
```bash
uv run pytest tests\wf_api\test_direct_service.py -q
```
Expected: fail because `WorkflowApi` still expects a `WorkflowApiBackend`.
- [ ] **Step 3: Rewrite `WorkflowApi.__init__`**
In `src/wf_api/service.py`, replace:
```python
from .backend import TraceRange, WorkflowApiBackend
```
with:
```python
from .artifacts import WorkflowArtifactApi
from .capabilities import WorkflowCapabilityApi
from .deployments import WorkflowDeploymentApi
from .drafts import WorkflowDraftApi
from .models import TraceRange
from .operation_context import WorkflowOperationContext
from .runs import TraceRangeLike, WorkflowRunApi
```
Replace the class docstring and constructor:
```python
class WorkflowApi:
"""Protocol-neutral workflow application facade.
This facade owns the stable application entry point. It composes the
domain APIs from a WorkflowOperationContext so MCP, CLI, and future HTTP
callers share one operation surface without importing wf_mcp.
"""
def __init__(self, context: WorkflowOperationContext) -> None:
self.context = context
self.capabilities = WorkflowCapabilityApi(context)
self.drafts = WorkflowDraftApi(context)
self.artifacts = WorkflowArtifactApi(context)
self.deployments = WorkflowDeploymentApi(context)
self.runs = WorkflowRunApi(context)
```
- [ ] **Step 4: Replace backend delegations with domain service delegations**
In `src/wf_api/service.py`, replace these groups:
Capabilities:
```python
self.backend.list_capabilities(...) -> self.capabilities.list_capabilities(...)
self.backend.inspect_capability(...) -> self.capabilities.inspect_capability(...)
self.backend.call_capability(...) -> self.capabilities.call_capability(...)
self.backend.create_draft_workspace_from_capability(...) -> self.capabilities.create_draft_workspace_from_capability(...)
```
Artifacts:
```python
self.backend.list_artifacts(...) -> self.artifacts.list_artifacts(...)
self.backend.inspect_artifact(...) -> self.artifacts.inspect_artifact(...)
self.backend.save_artifact(...) -> self.artifacts.save_artifact(...)
self.backend.create_artifact_from_plan(...) -> self.artifacts.create_artifact_from_plan(...)
self.backend.create_artifact_from_draft(...) -> self.artifacts.create_artifact_from_draft(...)
self.backend.create_artifact_from_workspace(...) -> self.artifacts.create_artifact_from_workspace(...)
self.backend.create_wrapper_from_workspace(...) -> self.artifacts.create_wrapper_from_workspace(...)
```
Drafts:
```python
self.backend.validate_draft(...) -> self.drafts.validate_draft(...)
self.backend.compile_draft(...) -> self.drafts.compile_draft(...)
self.backend.patch_draft(...) -> self.drafts.patch_draft(...)
self.backend.list_draft_workspaces() -> self.drafts.list_draft_workspaces()
self.backend.create_draft_workspace(...) -> self.drafts.create_draft_workspace(...)
self.backend.get_draft_workspace(...) -> self.drafts.get_draft_workspace(...)
self.backend.delete_draft_workspace(...) -> self.drafts.delete_draft_workspace(...)
self.backend.validate_draft_workspace(...) -> self.drafts.validate_draft_workspace(...)
self.backend.patch_draft_workspace(...) -> self.drafts.patch_draft_workspace(...)
self.backend.set_draft_name(...) -> self.drafts.set_draft_name(...)
self.backend.set_draft_route(...) -> self.drafts.set_draft_route(...)
self.backend.set_step_input_map(...) -> self.drafts.set_step_input_map(...)
self.backend.set_step_output_map(...) -> self.drafts.set_step_output_map(...)
self.backend.create_minimal_draft_workspace(...) -> self.drafts.create_minimal_draft_workspace(...)
```
Deployments:
```python
self.backend.list_deployments() -> self.deployments.list_deployments()
self.backend.inspect_deployment(...) -> self.deployments.inspect_deployment(...)
self.backend.save_deployment(...) -> self.deployments.save_deployment(...)
self.backend.delete_deployment(...) -> self.deployments.delete_deployment(...)
self.backend.validate_deployment(...) -> self.deployments.validate_deployment(...)
```
Runs:
```python
self.backend.run_deployment(...) -> self.runs.run_deployment(...)
self.backend.resume_run(...) -> self.runs.resume_run(...)
self.backend.inspect_run(...) -> self.runs.inspect_run(...)
self.backend.read_run_trace(...) -> self.runs.read_run_trace(...)
```
For run methods, change type hints from `TraceRange | None` to `TraceRangeLike | None` and `TraceRange` to `TraceRangeLike` so MCP Pydantic `TraceRange` and CLI dataclass `TraceRange` both remain accepted structurally.
- [ ] **Step 5: Verify Task 2**
Run:
```bash
uv run pytest tests\wf_api\test_direct_service.py tests\wf_api\test_capability_api.py 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
uv run ruff check src\wf_api\service.py tests\wf_api\test_direct_service.py
uv run ruff format --check src\wf_api\service.py tests\wf_api\test_direct_service.py
```
Expected: direct service tests and domain API tests pass.
---
### Task 3: Update CLI and MCP Tool Construction
**Files:**
- Modify: `src/wf_cli/context.py`
- Modify: `tests/wf_cli/test_context.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Add: `tests/wf_api/test_no_double_delegation.py`
- [ ] **Step 1: Write no-backend-chain test**
Create `tests/wf_api/test_no_double_delegation.py`:
```python
from __future__ import annotations
import ast
from pathlib import Path
def _imports_module(path: Path, module_name: str) -> bool:
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module == module_name:
return True
if isinstance(node, ast.Import):
if any(alias.name == module_name for alias in node.names):
return True
return False
def test_cli_and_mcp_tools_do_not_import_backend_adapter() -> None:
root = Path(__file__).resolve().parents[2]
assert not _imports_module(
root / "src" / "wf_cli" / "context.py",
"wf_mcp.broker.service.workflow_api_backend",
)
assert not _imports_module(
root / "src" / "wf_mcp" / "workflow_surface" / "tools.py",
"wf_mcp.broker.service.workflow_api_backend",
)
```
- [ ] **Step 2: Run failing no-backend-chain test**
Run:
```bash
uv run pytest tests\wf_api\test_no_double_delegation.py -q
```
Expected: fail because CLI context and MCP tools still import `WfMcpWorkflowApiBackend`.
- [ ] **Step 3: Update CLI context**
In `src/wf_cli/context.py`, remove:
```python
from wf_mcp.broker.service.workflow_api_backend import WfMcpWorkflowApiBackend
```
Add:
```python
from wf_mcp.broker.service.workflow_operation_context import context_from_service
```
Change `load_cli_context()`:
```python
handlers=WorkflowApi(context_from_service(service)),
```
- [ ] **Step 4: Update CLI context test**
In `tests/wf_cli/test_context.py`, replace the private backend-chain assertion:
```python
assert context.handlers.backend._handlers.service is context.service # type: ignore[attr-defined]
```
with:
```python
assert context.handlers.context.artifact_store is context.service.artifact_store
assert context.handlers.context.draft_workspace_store is context.service.draft_workspace_store
assert context.handlers.context.run_store is context.service.run_store
```
This tests the public context seam instead of the deleted backend chain.
- [ ] **Step 5: Update MCP workflow tools**
In `src/wf_mcp/workflow_surface/tools.py`, remove:
```python
from wf_mcp.broker.service.workflow_api_backend import WfMcpWorkflowApiBackend
```
Add:
```python
from wf_mcp.broker.service.workflow_operation_context import context_from_service
```
Change:
```python
handlers = WorkflowApi(WfMcpWorkflowApiBackend(service))
```
to:
```python
handlers = WorkflowApi(context_from_service(service))
```
For run tools, pass `request.trace_range` or `trace_range` directly to `handlers.*`. Remove conversions through `ApiTraceRange`.
- [ ] **Step 6: Verify Task 3**
Run:
```bash
uv run pytest tests\wf_api\test_no_double_delegation.py tests\wf_cli\test_context.py tests\wf_cli tests\wf_mcp\server\test_tools.py tests\wf_mcp\workflow_surface -q
uv run ruff check src\wf_cli\context.py src\wf_mcp\workflow_surface\tools.py tests\wf_cli\test_context.py tests\wf_api\test_no_double_delegation.py
uv run ruff format --check src\wf_cli\context.py src\wf_mcp\workflow_surface\tools.py tests\wf_cli\test_context.py tests\wf_api\test_no_double_delegation.py
```
Expected: CLI and MCP workflow tool tests pass.
---
### Task 4: Shrink `WorkflowSurfaceHandlers` to Compatibility Shim
**Files:**
- Modify: `src/wf_mcp/workflow_surface/handlers.py`
- Modify: `tests/wf_api/test_direct_service.py` or add a small handler shim test
- [ ] **Step 1: Add compatibility shim test**
Add to `tests/wf_api/test_direct_service.py`:
```python
def test_workflow_surface_handlers_is_compatibility_shim() -> None:
from wf_api import WorkflowApi
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
root = local_temp_root() / "workflow_surface_handler_shim"
service = WfMcpService(
store=FileStore(root / "mcp"),
artifact_store=FileWorkflowArtifactStore(root),
)
handlers = WorkflowSurfaceHandlers(service)
assert isinstance(handlers, WorkflowApi)
assert handlers.service is service
assert handlers.context.artifact_store is service.artifact_store
```
- [ ] **Step 2: Run failing compatibility test**
Run:
```bash
uv run pytest tests\wf_api\test_direct_service.py::test_workflow_surface_handlers_is_compatibility_shim -q
```
Expected: fail because `WorkflowSurfaceHandlers` is not a `WorkflowApi` subclass yet.
- [ ] **Step 3: Replace `WorkflowSurfaceHandlers` implementation**
Replace `src/wf_mcp/workflow_surface/handlers.py` with:
```python
from __future__ import annotations
from wf_api import WorkflowApi
from ..broker.service.workflow_operation_context import context_from_service
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..broker.service import WfMcpService
class WorkflowSurfaceHandlers(WorkflowApi):
"""Compatibility wrapper for old wf_mcp.workflow_surface imports.
New code should construct `WorkflowApi(context_from_service(service))`
directly. This shim keeps tests and legacy broker artifact tools working
while the MCP surface is migrated.
"""
def __init__(self, service: WfMcpService) -> None:
self.service = service
super().__init__(context_from_service(service))
__all__ = ["WorkflowSurfaceHandlers"]
```
This file should no longer import domain services directly.
- [ ] **Step 4: Verify handler compatibility**
Run:
```bash
uv run pytest tests\wf_api\test_direct_service.py::test_workflow_surface_handlers_is_compatibility_shim tests\wf_mcp\workflow_surface tests\wf_mcp\test_saved_subgraphs.py tests\wf_mcp\broker -q
uv run ruff check src\wf_mcp\workflow_surface\handlers.py tests\wf_api\test_direct_service.py
uv run ruff format --check src\wf_mcp\workflow_surface\handlers.py tests\wf_api\test_direct_service.py
```
Expected: old handler tests pass through the shim.
---
### Task 5: Delete Backend Protocol and Adapter
**Files:**
- Delete: `src/wf_api/backend.py`
- Delete: `src/wf_mcp/broker/service/workflow_api_backend.py`
- Modify: `src/wf_api/__init__.py`
- Modify docs that describe the old backend chain
- [ ] **Step 1: Delete backend files**
Delete:
```text
src/wf_api/backend.py
src/wf_mcp/broker/service/workflow_api_backend.py
```
- [ ] **Step 2: Remove public backend export**
In `src/wf_api/__init__.py`, ensure there is no import or `__all__` entry for `WorkflowApiBackend`.
- [ ] **Step 3: Search for live backend references**
Run:
```bash
rg -n "WorkflowApiBackend|WfMcpWorkflowApiBackend|workflow_api_backend|\\.backend" src tests
```
Expected: no live source/test references.
Historical docs under `docs/superpowers/plans/2026-06-01-*` may still mention the old slice. Do not rewrite historical plans except the active roadmap files named in Task 6.
- [ ] **Step 4: Verify deletion**
Run:
```bash
uv run pytest tests\wf_api\test_import_direction.py tests\wf_api\test_no_double_delegation.py tests\wf_api\test_cli_context_uses_api.py -q
uv run ruff check src\wf_api src\wf_cli\context.py src\wf_mcp\workflow_surface src\wf_mcp\broker\service tests\wf_api
uv run ruff format --check src\wf_api src\wf_cli\context.py src\wf_mcp\workflow_surface src\wf_mcp\broker\service tests\wf_api
```
Expected: tests pass, lint pass, format pass.
---
### Task 6: Update Active Docs
**Files:**
- Modify: `docs/current_roadmap.md`
- Modify: `docs/wf_mcp_architecture.md`
- Modify: `docs/superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md`
- [ ] **Step 1: Update `docs/current_roadmap.md`**
Replace the bullet that says the next useful slice is removing double-delegation with:
```markdown
- Double-delegation has been removed: CLI and MCP workflow tools construct
`WorkflowApi(context_from_service(service))` directly. `WorkflowSurfaceHandlers`
remains only as a temporary compatibility shim for older imports.
```
- [ ] **Step 2: Update `docs/wf_mcp_architecture.md`**
Find the architecture text that contains:
```text
wf_api.WorkflowApi ───> WorkflowApiBackend
```
Replace that diagram/text with:
```text
wf_mcp.workflow_surface.tools
-> wf_api.WorkflowApi
-> wf_api domain services
-> WorkflowOperationContext
-> WfMcpService adapters/stores/runtime
```
Add:
```markdown
`WorkflowSurfaceHandlers` is a compatibility shim only. New entrypoints should
construct `WorkflowApi(context_from_service(service))` directly.
```
- [ ] **Step 3: Update active extraction roadmap**
In `docs/superpowers/plans/2026-06-01-wf-api-extraction-roadmap.md`, add a current-state note near the top:
```markdown
> Current update: the original `WorkflowApiBackend` seam was useful for proving
> dependency direction, but has been collapsed. `WorkflowApi` now composes
> domain services directly from `WorkflowOperationContext`; MCP owns only
> context construction and tool schemas.
```
Do not rewrite the historical task bodies. They describe prior slices.
- [ ] **Step 4: Verify docs**
Run:
```bash
git diff --check -- docs\current_roadmap.md docs\wf_mcp_architecture.md docs\superpowers\plans\2026-06-01-wf-api-extraction-roadmap.md
```
Expected: no whitespace errors.
---
### Task 7: Final Verification
**Files:**
- All touched files.
- [ ] **Step 1: Run focused workflow API/MCP/CLI tests**
Run:
```bash
uv run pytest tests\wf_api tests\wf_cli tests\wf_mcp\workflow_surface tests\wf_mcp\server\test_tools.py tests\wf_mcp\test_saved_subgraphs.py -q
```
Expected: selected tests pass.
- [ ] **Step 2: Run full suite**
Run:
```bash
uv run pytest -q
```
Expected: full suite passes with known skip/xfail counts.
- [ ] **Step 3: Run lint and format checks**
Run:
```bash
uv run ruff check src\wf_api src\wf_cli src\wf_mcp tests\wf_api tests\wf_cli tests\wf_mcp
uv run ruff format --check src\wf_api src\wf_cli src\wf_mcp tests\wf_api tests\wf_cli 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.
- [ ] **Step 5: Final reference check**
Run:
```bash
rg -n "WorkflowApiBackend|WfMcpWorkflowApiBackend|workflow_api_backend|\\.backend" src tests docs\current_roadmap.md docs\wf_mcp_architecture.md
```
Expected: no references in live source/tests/current docs.
---
## Self-Review
- Spec coverage: The plan removes `WorkflowApiBackend`, deletes `WfMcpWorkflowApiBackend`, updates CLI/MCP construction, keeps handler compatibility, moves `TraceRange`, and updates active docs.
- Placeholder scan: No `TODO`/`TBD` placeholders remain.
- Type consistency: `WorkflowApi` accepts `WorkflowOperationContext`; run trace methods accept `TraceRangeLike`; `TraceRange` is a convenience DTO exported from `wf_api.models`.
- Scope check: This does not remove all `WorkflowSurfaceHandlers` tests or legacy imports. It reduces handlers to a shim; deleting the shim is a later cleanup once `wf_mcp.broker.artifact_tools` and legacy tests stop importing it.