504 lines
12 KiB
Markdown
504 lines
12 KiB
Markdown
# wf_api Slice 3A: Refs And Constants Move 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 the protocol-neutral workflow API refs and constants helpers from `wf_mcp.workflow_surface` into `wf_api`, while preserving old imports as compatibility shims.
|
||
|
||
**Architecture:** `wf_api` is now the canonical home for process-local workflow API helpers that are not MCP-specific. This slice moves only `constants.py` and `refs.py` because both are small and import only `wf_artifacts` / `wf_platform`. `wf_mcp.workflow_surface.constants` and `wf_mcp.workflow_surface.refs` remain thin re-export shims so existing imports keep working.
|
||
|
||
**Tech Stack:** Python 3.14+, Pydantic-backed refs from `wf_artifacts` and `wf_platform`, pytest, ruff, basedpyright.
|
||
|
||
---
|
||
|
||
## Scope
|
||
|
||
### In Scope
|
||
|
||
- Create `src/wf_api/constants.py`.
|
||
- Create `src/wf_api/refs.py`.
|
||
- Re-export the new helpers from `src/wf_api/__init__.py`.
|
||
- Replace `wf_mcp.workflow_surface.constants` with a compatibility shim.
|
||
- Replace `wf_mcp.workflow_surface.refs` with a compatibility shim.
|
||
- Update `src/wf_mcp/workflow_surface/handlers.py` to import canonical helpers from `wf_api`.
|
||
- Add/adjust tests for canonical imports and shim compatibility.
|
||
- Keep `wf_api` free of `wf_mcp` imports.
|
||
|
||
### Out Of Scope
|
||
|
||
- Do not move `models.py`.
|
||
- Do not move `next_actions.py`.
|
||
- Do not move `wrapper_hints.py`.
|
||
- Do not move `run_lifecycle.py`.
|
||
- Do not move `runtime_dependencies.py`.
|
||
- Do not move `saved_subgraphs.py`.
|
||
- Do not rename `WorkflowSurfaceHandlers`.
|
||
- Do not change public payloads, tool names, command names, or parsing behavior.
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
### New Canonical Files
|
||
|
||
| File | Responsibility |
|
||
| --- | --- |
|
||
| `src/wf_api/constants.py` | Canonical workflow API literals used by draft/helper code. |
|
||
| `src/wf_api/refs.py` | Canonical parser for workflow-surface capability IDs. |
|
||
|
||
### Compatibility Shims
|
||
|
||
| File | Responsibility |
|
||
| --- | --- |
|
||
| `src/wf_mcp/workflow_surface/constants.py` | Re-export constants from `wf_api.constants`; no local logic. |
|
||
| `src/wf_mcp/workflow_surface/refs.py` | Re-export refs from `wf_api.refs`; no local logic. |
|
||
|
||
### Modified Consumers
|
||
|
||
| File | Change |
|
||
| --- | --- |
|
||
| `src/wf_api/__init__.py` | Re-export moved helpers. |
|
||
| `src/wf_mcp/workflow_surface/handlers.py` | Import constants and parser from `wf_api`. |
|
||
| `tests/wf_mcp/test_workflow_surface_refs.py` | Keep shim compatibility tests and add canonical import tests. |
|
||
| `tests/wf_api/test_import_direction.py` | Existing guard should continue to pass. |
|
||
|
||
---
|
||
|
||
## Task 1: Add Canonical `wf_api.constants`
|
||
|
||
**Files:**
|
||
|
||
- Create: `src/wf_api/constants.py`
|
||
|
||
- [ ] **Step 1: Create `src/wf_api/constants.py`**
|
||
|
||
Write this exact file:
|
||
|
||
```python
|
||
"""Protocol-neutral workflow API literals used by draft/helper code."""
|
||
|
||
DEFAULT_CALL_STEP_ID = "call"
|
||
DEFAULT_ERROR_STEP_ID = "tool_error"
|
||
DEFAULT_OK_OUTCOME = "ok"
|
||
DEFAULT_ERROR_OUTCOME = "error"
|
||
RUNTIME_ERROR_CAPABILITY = "wf.std.runtime_error"
|
||
|
||
__all__ = [
|
||
"DEFAULT_CALL_STEP_ID",
|
||
"DEFAULT_ERROR_OUTCOME",
|
||
"DEFAULT_ERROR_STEP_ID",
|
||
"DEFAULT_OK_OUTCOME",
|
||
"RUNTIME_ERROR_CAPABILITY",
|
||
]
|
||
```
|
||
|
||
- [ ] **Step 2: Run import smoke check**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
uv run python -c "from wf_api.constants import DEFAULT_CALL_STEP_ID, RUNTIME_ERROR_CAPABILITY; print(DEFAULT_CALL_STEP_ID, RUNTIME_ERROR_CAPABILITY)"
|
||
```
|
||
|
||
Expected output:
|
||
|
||
```text
|
||
call wf.std.runtime_error
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: Add Canonical `wf_api.refs`
|
||
|
||
**Files:**
|
||
|
||
- Create: `src/wf_api/refs.py`
|
||
|
||
- [ ] **Step 1: Create `src/wf_api/refs.py`**
|
||
|
||
Write this exact file:
|
||
|
||
```python
|
||
from __future__ import annotations
|
||
|
||
from typing import Any, TypeAlias
|
||
|
||
from wf_artifacts import WorkflowCapabilityRef
|
||
from wf_platform import CapabilityRef
|
||
|
||
WorkflowSurfaceCapabilityId: TypeAlias = CapabilityRef | WorkflowCapabilityRef
|
||
|
||
|
||
def parse_workflow_surface_capability_id(
|
||
value: str | dict[str, Any],
|
||
) -> WorkflowSurfaceCapabilityId:
|
||
"""Parse a workflow API capability id into its real domain ref.
|
||
|
||
API callers still pass strings at protocol boundaries. Internally,
|
||
workflow-facing capability ids are either live source capabilities or saved
|
||
wrapper artifacts, so this parser avoids inventing a third identifier model.
|
||
"""
|
||
if isinstance(value, dict):
|
||
if "artifact_id" in value and "version" in value:
|
||
return WorkflowCapabilityRef._validate(value)
|
||
return CapabilityRef._validate(value)
|
||
|
||
try:
|
||
return WorkflowCapabilityRef.parse(value)
|
||
except ValueError:
|
||
return CapabilityRef.parse(value)
|
||
```
|
||
|
||
- [ ] **Step 2: Run import smoke check**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
uv run python -c "from wf_api.refs import parse_workflow_surface_capability_id; print(parse_workflow_surface_capability_id('workflow.echo_wrapper.v2'))"
|
||
```
|
||
|
||
Expected output:
|
||
|
||
```text
|
||
workflow.echo_wrapper.v2
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: Re-export Helpers From `wf_api`
|
||
|
||
**Files:**
|
||
|
||
- Modify: `src/wf_api/__init__.py`
|
||
|
||
- [ ] **Step 1: Update imports and `__all__`**
|
||
|
||
Change `src/wf_api/__init__.py` to include these imports:
|
||
|
||
```python
|
||
from .constants import (
|
||
DEFAULT_CALL_STEP_ID,
|
||
DEFAULT_ERROR_OUTCOME,
|
||
DEFAULT_ERROR_STEP_ID,
|
||
DEFAULT_OK_OUTCOME,
|
||
RUNTIME_ERROR_CAPABILITY,
|
||
)
|
||
from .refs import WorkflowSurfaceCapabilityId, parse_workflow_surface_capability_id
|
||
```
|
||
|
||
Ensure `__all__` includes:
|
||
|
||
```python
|
||
__all__ = [
|
||
"DEFAULT_CALL_STEP_ID",
|
||
"DEFAULT_ERROR_OUTCOME",
|
||
"DEFAULT_ERROR_STEP_ID",
|
||
"DEFAULT_OK_OUTCOME",
|
||
"RUNTIME_ERROR_CAPABILITY",
|
||
"TraceRange",
|
||
"WorkflowApi",
|
||
"WorkflowApiBackend",
|
||
"WorkflowSurfaceCapabilityId",
|
||
"parse_workflow_surface_capability_id",
|
||
]
|
||
```
|
||
|
||
- [ ] **Step 2: Run import smoke check**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
uv run python -c "from wf_api import DEFAULT_OK_OUTCOME, parse_workflow_surface_capability_id; print(DEFAULT_OK_OUTCOME, parse_workflow_surface_capability_id('demo.personal.echo_tool'))"
|
||
```
|
||
|
||
Expected output:
|
||
|
||
```text
|
||
ok demo.personal.echo_tool
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: Convert Old Workflow-Surface Modules To Shims
|
||
|
||
**Files:**
|
||
|
||
- Modify: `src/wf_mcp/workflow_surface/constants.py`
|
||
- Modify: `src/wf_mcp/workflow_surface/refs.py`
|
||
|
||
- [ ] **Step 1: Replace `src/wf_mcp/workflow_surface/constants.py`**
|
||
|
||
Replace the file with this shim:
|
||
|
||
```python
|
||
"""Compatibility shim for workflow API constants.
|
||
|
||
New code should import these literals from `wf_api.constants`. This module stays
|
||
so older MCP workflow-surface imports keep working during extraction.
|
||
"""
|
||
|
||
from wf_api.constants import (
|
||
DEFAULT_CALL_STEP_ID,
|
||
DEFAULT_ERROR_OUTCOME,
|
||
DEFAULT_ERROR_STEP_ID,
|
||
DEFAULT_OK_OUTCOME,
|
||
RUNTIME_ERROR_CAPABILITY,
|
||
)
|
||
|
||
__all__ = [
|
||
"DEFAULT_CALL_STEP_ID",
|
||
"DEFAULT_ERROR_OUTCOME",
|
||
"DEFAULT_ERROR_STEP_ID",
|
||
"DEFAULT_OK_OUTCOME",
|
||
"RUNTIME_ERROR_CAPABILITY",
|
||
]
|
||
```
|
||
|
||
- [ ] **Step 2: Replace `src/wf_mcp/workflow_surface/refs.py`**
|
||
|
||
Replace the file with this shim:
|
||
|
||
```python
|
||
"""Compatibility shim for workflow API capability refs.
|
||
|
||
New code should import from `wf_api.refs`. This module stays so older MCP
|
||
workflow-surface imports keep working during extraction.
|
||
"""
|
||
|
||
from wf_api.refs import (
|
||
WorkflowSurfaceCapabilityId,
|
||
parse_workflow_surface_capability_id,
|
||
)
|
||
|
||
__all__ = [
|
||
"WorkflowSurfaceCapabilityId",
|
||
"parse_workflow_surface_capability_id",
|
||
]
|
||
```
|
||
|
||
- [ ] **Step 3: Run shim import smoke check**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
uv run python -c "from wf_mcp.workflow_surface.constants import DEFAULT_CALL_STEP_ID; from wf_mcp.workflow_surface.refs import parse_workflow_surface_capability_id; print(DEFAULT_CALL_STEP_ID, parse_workflow_surface_capability_id('workflow.echo_wrapper.v2'))"
|
||
```
|
||
|
||
Expected output:
|
||
|
||
```text
|
||
call workflow.echo_wrapper.v2
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: Update Canonical Imports In `WorkflowSurfaceHandlers`
|
||
|
||
**Files:**
|
||
|
||
- Modify: `src/wf_mcp/workflow_surface/handlers.py`
|
||
|
||
- [ ] **Step 1: Change constants import**
|
||
|
||
Replace:
|
||
|
||
```python
|
||
from .constants import (
|
||
DEFAULT_CALL_STEP_ID,
|
||
DEFAULT_ERROR_OUTCOME,
|
||
DEFAULT_ERROR_STEP_ID,
|
||
DEFAULT_OK_OUTCOME,
|
||
RUNTIME_ERROR_CAPABILITY,
|
||
)
|
||
```
|
||
|
||
with:
|
||
|
||
```python
|
||
from wf_api.constants import (
|
||
DEFAULT_CALL_STEP_ID,
|
||
DEFAULT_ERROR_OUTCOME,
|
||
DEFAULT_ERROR_STEP_ID,
|
||
DEFAULT_OK_OUTCOME,
|
||
RUNTIME_ERROR_CAPABILITY,
|
||
)
|
||
```
|
||
|
||
- [ ] **Step 2: Change refs import**
|
||
|
||
Replace:
|
||
|
||
```python
|
||
from .refs import parse_workflow_surface_capability_id
|
||
```
|
||
|
||
with:
|
||
|
||
```python
|
||
from wf_api.refs import parse_workflow_surface_capability_id
|
||
```
|
||
|
||
- [ ] **Step 3: Run import smoke check**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
uv run python -c "from wf_mcp.workflow_surface.handlers import WorkflowSurfaceHandlers; print(WorkflowSurfaceHandlers.__name__)"
|
||
```
|
||
|
||
Expected output:
|
||
|
||
```text
|
||
WorkflowSurfaceHandlers
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: Update Ref Tests For Canonical And Shim Imports
|
||
|
||
**Files:**
|
||
|
||
- Modify: `tests/wf_mcp/test_workflow_surface_refs.py`
|
||
|
||
- [ ] **Step 1: Update imports**
|
||
|
||
Change the top imports to:
|
||
|
||
```python
|
||
from wf_api.refs import parse_workflow_surface_capability_id
|
||
from wf_artifacts import WorkflowCapabilityRef
|
||
from wf_mcp.workflow_surface.refs import (
|
||
parse_workflow_surface_capability_id as parse_workflow_surface_capability_id_shim,
|
||
)
|
||
from wf_platform import CapabilityRef
|
||
```
|
||
|
||
- [ ] **Step 2: Add shim compatibility test**
|
||
|
||
Append this test to the file:
|
||
|
||
```python
|
||
def test_workflow_surface_refs_shim_reexports_canonical_parser() -> None:
|
||
assert parse_workflow_surface_capability_id_shim is parse_workflow_surface_capability_id
|
||
```
|
||
|
||
- [ ] **Step 3: Add constants shim compatibility test**
|
||
|
||
Append this test to the file:
|
||
|
||
```python
|
||
def test_workflow_surface_constants_shim_reexports_canonical_literals() -> None:
|
||
from wf_api.constants import DEFAULT_CALL_STEP_ID
|
||
from wf_mcp.workflow_surface.constants import (
|
||
DEFAULT_CALL_STEP_ID as DEFAULT_CALL_STEP_ID_SHIM,
|
||
)
|
||
|
||
assert DEFAULT_CALL_STEP_ID_SHIM == DEFAULT_CALL_STEP_ID
|
||
```
|
||
|
||
- [ ] **Step 4: Run focused tests**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
uv run pytest tests/wf_mcp/test_workflow_surface_refs.py tests/wf_api/test_import_direction.py -q
|
||
```
|
||
|
||
Expected: all tests pass.
|
||
|
||
---
|
||
|
||
## Task 7: Search For Remaining Canonical Import Opportunities
|
||
|
||
**Files:**
|
||
|
||
- Inspect only unless the search finds new low-risk direct consumers.
|
||
|
||
- [ ] **Step 1: Search old imports**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
rg -n "from \\.constants|from \\.refs|from wf_mcp\\.workflow_surface\\.(constants|refs)" src tests
|
||
```
|
||
|
||
Expected remaining matches:
|
||
|
||
```text
|
||
src/wf_mcp/workflow_surface/constants.py
|
||
src/wf_mcp/workflow_surface/refs.py
|
||
tests/wf_mcp/test_workflow_surface_refs.py
|
||
```
|
||
|
||
If any other production module imports the old paths, update it to import from
|
||
`wf_api.constants` or `wf_api.refs`.
|
||
|
||
- [ ] **Step 2: Search for accidental `wf_api -> wf_mcp` imports**
|
||
|
||
Run:
|
||
|
||
```powershell
|
||
rg -n "wf_mcp" src/wf_api tests/wf_api
|
||
```
|
||
|
||
Expected matches only in test text/docstrings for the import-direction guard,
|
||
not in `src/wf_api/*.py`.
|
||
|
||
---
|
||
|
||
## Task 8: Verification
|
||
|
||
- [ ] **Step 1: Run focused tests**
|
||
|
||
```powershell
|
||
uv run pytest tests/wf_api tests/wf_mcp/test_workflow_surface_refs.py tests/wf_mcp/workflow_surface -q
|
||
```
|
||
|
||
Expected: all pass.
|
||
|
||
- [ ] **Step 2: Run CLI context smoke tests**
|
||
|
||
```powershell
|
||
uv run pytest tests/wf_cli/test_context.py -q
|
||
```
|
||
|
||
Expected: pass.
|
||
|
||
- [ ] **Step 3: Run ruff on touched files**
|
||
|
||
```powershell
|
||
uv run ruff check src/wf_api src/wf_mcp/workflow_surface/constants.py src/wf_mcp/workflow_surface/refs.py src/wf_mcp/workflow_surface/handlers.py tests/wf_mcp/test_workflow_surface_refs.py tests/wf_api
|
||
```
|
||
|
||
Expected: all checks pass.
|
||
|
||
- [ ] **Step 4: Run basedpyright on touched files**
|
||
|
||
```powershell
|
||
uv run basedpyright --level error src/wf_api src/wf_mcp/workflow_surface/constants.py src/wf_mcp/workflow_surface/refs.py src/wf_mcp/workflow_surface/handlers.py tests/wf_mcp/test_workflow_surface_refs.py tests/wf_api
|
||
```
|
||
|
||
Expected: `0 errors`.
|
||
|
||
- [ ] **Step 5: Optional full suite**
|
||
|
||
Run this if time allows:
|
||
|
||
```powershell
|
||
uv run pytest -q
|
||
```
|
||
|
||
Expected: full suite passes with the project’s existing skipped/xfailed counts.
|
||
|
||
---
|
||
|
||
## Self-Review Checklist
|
||
|
||
- `wf_api.constants` imports no `wf_mcp`.
|
||
- `wf_api.refs` imports no `wf_mcp`.
|
||
- Old `wf_mcp.workflow_surface.constants` import path still works.
|
||
- Old `wf_mcp.workflow_surface.refs` import path still works.
|
||
- `WorkflowSurfaceHandlers` imports the canonical `wf_api` helpers.
|
||
- No public payload shape changed.
|
||
- No behavior changed.
|
||
- No other workflow-surface helper moved in this slice.
|