docs: plan capability step updates

This commit is contained in:
lda
2026-07-26 23:11:18 +07:00 Verified
parent 05afc2e1d1
commit de88a04e05
@@ -0,0 +1,978 @@
# Capability Step Update 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:** Add one atomic, presence-aware capability-step update across Python,
JSON-RPC, MCP, and CLI, and let capability-step creation set the same metadata
and canonical literal inputs directly.
**Architecture:** Define a transport-safe `CapabilityStepUpdate` model in
`wf_api`, then keep all semantic mutation in `WorkflowDraftAuthoringApi`.
Extract the existing capability-aware input-binding preflight into one private
implementation reused by set-input, update, and creation. RPC, MCP, and CLI are
adapters that preserve omitted fields, explicit metadata nulls, and canonical
binding order.
**Tech Stack:** Python 3.14, Pydantic 2, Typer, FastAPI JSON-RPC, FastMCP,
pytest, Ruff, basedpyright.
## Global Constraints
- Never change an existing step's `use` capability.
- Preserve routes, output bindings, and unrelated step fields.
- Omitted update fields preserve stored values.
- Explicit metadata `null` clears; explicit `input: null` is invalid.
- Supplied `input` replaces the complete ordered canonical list; `[]` clears.
- Reuse one capability-aware input preflight for set-input, update, and add.
- Stale revision wins over semantic step, capability, path, and schema errors.
- Metadata-only updates must not resolve the capability source.
- Exact no-ops do not increment revision.
- Input clearing does not delete previously projected workflow schemas.
- Keep compatibility `input_map` for real callers, but reject simultaneous
compatibility and canonical input forms.
- Keep routes and output bindings under their existing focused operations.
- Python baseline is 3.14. Add comments/docstrings around field-presence and
schema-projection behavior that is not obvious.
- Use scoped tests in each task and the complete focused matrix only in Task 5.
---
### Task 1: Presence-Aware Model And Canonical Authoring
**Files:**
- Create: `src/wf_api/draft_updates.py`
- Modify: `src/wf_api/__init__.py`
- Modify: `src/wf_api/draft_authoring.py`
- Modify: `tests/wf_api/test_drafts_service.py`
**Interfaces:**
- Produces:
`CapabilityStepUpdate(desc, retry, timeout_seconds, input)`.
- Produces:
`WorkflowDraftAuthoringApi.update_capability_step(*, workspace_id: str,
revision: int, step_id: str, update: CapabilityStepUpdate)`.
- Extends:
`WorkflowDraftAuthoringApi.add_step_from_capability(..., desc=None,
retry=None, timeout_seconds=None, input_bindings=None)`.
- Preserves:
`WorkflowDraftAuthoringApi.set_step_input_bindings(...)`.
- [ ] **Step 1: Write failing update-model tests**
Add focused tests in `tests/wf_api/test_drafts_service.py`:
```python
def test_capability_step_update_preserves_field_presence() -> None:
update = CapabilityStepUpdate.model_validate(
{"desc": None, "retry": 0, "input": []}
)
assert update.model_fields_set == {"desc", "retry", "input"}
assert update.desc is None
assert update.retry == 0
assert update.input == []
@pytest.mark.parametrize(
"payload",
[
{},
{"input": None},
{"desc": ""},
{"retry": -1},
{"timeout_seconds": 0},
],
)
def test_capability_step_update_rejects_invalid_patch(payload: object) -> None:
with pytest.raises(ValidationError):
CapabilityStepUpdate.model_validate(payload)
```
- [ ] **Step 2: Run the model tests red**
Run:
```powershell
.venv\Scripts\python.exe -m pytest -n 0 `
tests/wf_api/test_drafts_service.py `
-q -k "capability_step_update_preserves or capability_step_update_rejects" `
--basetemp=.pytest-capability-update-model-red
```
Expected: collection fails because `CapabilityStepUpdate` does not exist.
- [ ] **Step 3: Implement the update model**
Create `src/wf_api/draft_updates.py`:
```python
from __future__ import annotations
from typing import Self
from pydantic import BaseModel, ConfigDict, Field, model_validator
from wf_core.models.steps import InputBinding
class CapabilityStepUpdate(BaseModel):
"""Presence-aware patch for one existing capability-backed draft step."""
model_config = ConfigDict(extra="forbid")
desc: str | None = Field(default=None, min_length=1)
retry: int | None = Field(default=None, ge=0)
timeout_seconds: int | None = Field(default=None, gt=0)
input: list[InputBinding] | None = None
@model_validator(mode="after")
def validate_patch_shape(self) -> Self:
if not self.model_fields_set:
raise ValueError("capability step update requires at least one field")
if "input" in self.model_fields_set and self.input is None:
raise ValueError("capability step update input must be a list")
return self
```
Export it from `src/wf_api/__init__.py`. Run the model tests green.
- [ ] **Step 4: Write failing semantic update tests**
Add a helper that creates a capability-backed draft step containing:
- `use`;
- path and literal input bindings;
- output bindings;
- `desc`;
- `retry`;
- `timeout_seconds`;
- routes.
Add tests with field-level assertions:
```python
@pytest.mark.asyncio
async def test_update_capability_step_changes_metadata_and_inputs_atomically(
tmp_path: Path,
) -> None:
draft_api, _, authoring = _draft_api(
FileWorkflowArtifactStore(tmp_path / "update_capability"),
register_echo=True,
)
draft = _echo_draft()
draft["steps"]["echo"].update(
{
"desc": "Old description",
"retry": 1,
"timeout_seconds": 10,
}
)
await draft_api.create_draft_workspace(workspace_id="echo", draft=draft)
result = await authoring.update_capability_step(
workspace_id="echo",
revision=1,
step_id="echo",
update=CapabilityStepUpdate.model_validate(
{
"desc": "New description",
"retry": 0,
"timeout_seconds": None,
"input": [
{"path": "input.text", "target": "text"},
],
}
),
)
inspected = await draft_api.get_draft_workspace(
workspace_id="echo",
include_draft=True,
)
step = inspected["draft"]["steps"]["echo"]
assert result["revision"] == 2
assert step["use"] == "demo.personal.echo_tool"
assert step["desc"] == "New description"
assert step["retry"] == 0
assert "timeout_seconds" not in step
assert step["input"] == [{"path": "input.text", "target": "text"}]
assert step["output"] == draft["steps"]["echo"]["output"]
assert inspected["draft"]["routes"]["echo"] == {"ok": "__end__"}
```
Add separate tests for:
- omitted fields preserved;
- each metadata field set and cleared;
- exact no-op keeps revision;
- `input=[]` clears bindings but preserves projected schemas;
- metadata-only update succeeds when capability lookup is monkeypatched to
fail;
- missing step and non-`DraftUseStep` rejection;
- stale revision wins over missing step, wrong kind, and unavailable
capability;
- malformed input leaves the complete inspected workspace unchanged;
- path/literal input replacement projects schemas and preserves order;
- compile/run succeeds after one combined metadata/input update.
- [ ] **Step 5: Run semantic tests red**
Run:
```powershell
.venv\Scripts\python.exe -m pytest -n 0 `
tests/wf_api/test_drafts_service.py `
-q -k "update_capability_step" `
--basetemp=.pytest-capability-update-authoring-red
```
Expected: failures because the authoring method does not exist.
- [ ] **Step 6: Extract one shared input-binding preflight**
In `src/wf_api/draft_authoring.py`, add a private result model:
```python
@dataclass(frozen=True)
class _ProjectedStepInputBindings:
payload: list[dict[str, Any]]
input_schema: dict[str, Any]
state_schema: dict[str, Any]
```
Extract the current semantic body of `set_step_input_bindings` into a private
method:
```python
def _project_step_input_bindings(
self,
*,
workspace: WorkflowDraftWorkspace,
capability_name: str,
bindings: Sequence[InputBinding],
) -> _ProjectedStepInputBindings:
"""Validate canonical inputs and project missing workflow source schemas."""
```
This method must:
1. resolve the capability;
2. reject overlapping local targets;
3. require every target in the capability input schema;
4. validate literal values, including root-object behavior;
5. preserve `context.*` without schema projection;
6. project missing `input.*` and `state.*` source schemas;
7. return canonical binding payload and projected schemas.
Rewrite `set_step_input_bindings` to call this method, preserving its no-op and
patch behavior. Run all existing `set_step_input_bindings` tests before adding
the update method:
```powershell
.venv\Scripts\python.exe -m pytest -n 0 `
tests/wf_api/test_drafts_service.py `
-q -k "step_input_bindings" `
--basetemp=.pytest-capability-update-shared-preflight
```
Expected: PASS.
- [ ] **Step 7: Implement atomic capability-step update**
Implement:
```python
async def update_capability_step(
self,
*,
workspace_id: str,
revision: int,
step_id: str,
update: CapabilityStepUpdate,
) -> dict[str, Any]:
"""Patch capability metadata and optional canonical inputs atomically."""
```
Required implementation shape:
```python
checked = self._workspace_if_revision_matches(
workspace_id=workspace_id,
revision=revision,
)
if isinstance(checked, dict):
return checked
workspace = checked
step = draft_step(workspace.draft, step_id)
if step.get("kind") != "use":
raise ValueError(f"step {step_id!r} is not capability-backed")
current = DraftUseStep.model_validate(step)
changes: dict[str, object] = {}
for field in ("desc", "retry", "timeout_seconds"):
if field in update.model_fields_set:
changes[field] = getattr(update, field)
projected = None
if "input" in update.model_fields_set:
assert update.input is not None
projected = self._project_step_input_bindings(
workspace=workspace,
capability_name=current.use,
bindings=update.input,
)
changes["input"] = update.input
changed = current.model_copy(update=changes)
step_payload = changed.model_dump(
mode="json",
by_alias=True,
exclude_none=True,
)
```
Do not use `model_copy(update=...)` with unvalidated external values; all
values here come from `CapabilityStepUpdate`. Build one patch for changed
schemas plus replacement of `/steps/<step_id>`. If the step payload and
schemas are unchanged, return `summarize_draft_workspace(workspace)`.
- [ ] **Step 8: Write failing creation-parity tests**
Add tests proving:
```python
result = await authoring.add_step_from_capability(
workspace_id="report",
revision=1,
step_id="publish",
capability_name="demo.personal.structured_report",
routes={"ok": "__end__"},
desc="Publish report",
retry=2,
timeout_seconds=30,
input_bindings=[
InputPathBinding(
path=GraphSourcePath.state("report", "title"),
target=LocalPath.of("request", "title"),
),
InputValueBinding(
target=LocalPath.of("request", "format"),
value="markdown",
),
],
)
```
Assert metadata, ordered canonical bindings, and projected schemas. Add tests
rejecting `input_map` and `input_bindings` whenever both arguments are
supplied, including an explicit empty compatibility map, and proving existing
map-only callers retain behavior.
- [ ] **Step 9: Extend creation through the shared preflight**
Extend the method signature:
```python
async def add_step_from_capability(
...,
input_map: dict[str, str] | None = None,
input_bindings: Sequence[InputBinding] | None = None,
bind_outputs: dict[str, str] | None = None,
desc: str | None = None,
retry: int | None = None,
timeout_seconds: int | None = None,
) -> dict[str, Any]:
```
If both canonical bindings and a compatibility map are supplied, even when
either collection is explicitly empty, raise:
```text
input_map and input_bindings are mutually exclusive
```
Lower compatibility `input_map` with `input_bindings_payload(input_map, {})`
and validate the resulting typed list, or accept `input_bindings` directly.
Use `_project_step_input_bindings` for both. Persist metadata only when not
`None`; `retry=0` must survive.
- [ ] **Step 10: Verify and commit Task 1**
Run:
```powershell
.venv\Scripts\python.exe -m pytest -n 0 `
tests/wf_api/test_drafts_service.py -q `
--basetemp=.pytest-capability-update-task1
.venv\Scripts\ruff.exe check `
src/wf_api/draft_updates.py `
src/wf_api/draft_authoring.py `
tests/wf_api/test_drafts_service.py
.venv\Scripts\basedpyright.exe --level error `
src/wf_api/draft_updates.py `
src/wf_api/draft_authoring.py
```
Then:
```bash
git add src/wf_api/__init__.py src/wf_api/draft_updates.py \
src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py
git commit -m "feat: update capability-backed draft steps"
```
---
### Task 2: Public Interface And JSON-RPC
**Files:**
- Modify: `src/wf_api/surface.py`
- Modify: `src/wf_api/service.py`
- Modify: `src/wf_transport_rpc_http/models.py`
- Modify: `src/wf_transport_rpc_http/methods/drafts.py`
- Modify: `src/wf_transport_rpc_http/client/drafts.py`
- Modify: `src/wf_transport_rpc_http/__init__.py`
- Modify: `tests/wf_transport_rpc_http/test_app.py`
- Modify: `tests/wf_transport_rpc_http/test_client.py`
**Interfaces:**
- Consumes: Task 1's `CapabilityStepUpdate`,
`update_capability_step(...)`, and extended `add_step_from_capability(...)`.
- Produces:
`workflow.draft_workspaces.update_capability_step`.
- Extends the existing add-capability RPC with metadata and canonical
`input_bindings`.
- [ ] **Step 1: Write failing RPC model tests**
Add tests for:
```python
params = UpdateCapabilityStepParams.model_validate(
{
"workspace_id": "report",
"revision": 4,
"step_id": "publish",
"update": {"desc": None, "retry": 0},
}
)
assert params.update.model_fields_set == {"desc", "retry"}
assert params.update.desc is None
assert params.update.retry == 0
```
Add malformed cases for empty update, `input:null`, negative retry, zero
timeout, and unknown fields. Extend `AddStepFromCapabilityParams` tests for
metadata, canonical path/value order, and simultaneous `input_map` plus
`input_bindings`.
- [ ] **Step 2: Run RPC model tests red**
Run the focused new tests. Expected: import/model failures.
- [ ] **Step 3: Add RPC models**
Add:
```python
class UpdateCapabilityStepParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
revision: int = Field(ge=1)
step_id: str = Field(min_length=1)
update: CapabilityStepUpdate
```
Extend `AddStepFromCapabilityParams`:
```python
input_map: dict[str, str] | None = None
input_bindings: list[InputBinding] | None = None
desc: str | None = Field(default=None, min_length=1)
retry: int | None = Field(default=None, ge=0)
timeout_seconds: int | None = Field(default=None, gt=0)
```
Add a model validator rejecting both input forms when both were supplied.
- [ ] **Step 4: Write failing endpoint and client tests**
Add a real ASGI test that creates a capability step, updates metadata and a
literal input through:
```text
workflow.draft_workspaces.update_capability_step
```
Inspect the draft and assert preservation of `use`, routes, and outputs.
Add a client serialization test that passes:
```python
CapabilityStepUpdate.model_validate({"desc": None, "retry": 0})
```
and asserts the emitted nested object is exactly:
```python
{"desc": None, "retry": 0}
```
No default `timeout_seconds` or `input` keys may appear. Extend add-capability
client tests for canonical input order and metadata.
- [ ] **Step 5: Implement public and RPC adapters**
Add the update method to `WorkflowDraftSurface` and `WorkflowApi`, delegating to
`draft_authoring`.
Register the RPC method and client. The client must serialize:
```python
update.model_dump(mode="json", exclude_unset=True)
```
Extend every add-capability seam with the Task 1 fields. Compatibility map-only
calls remain valid.
- [ ] **Step 6: Verify and commit Task 2**
Run:
```powershell
.venv\Scripts\python.exe -m pytest -n 0 `
tests/wf_transport_rpc_http/test_app.py `
tests/wf_transport_rpc_http/test_client.py `
-q --basetemp=.pytest-capability-update-task2
.venv\Scripts\ruff.exe check src/wf_api src/wf_transport_rpc_http `
tests/wf_transport_rpc_http
.venv\Scripts\basedpyright.exe --level error
```
Commit:
```bash
git add src/wf_api/surface.py src/wf_api/service.py \
src/wf_transport_rpc_http tests/wf_transport_rpc_http
git commit -m "feat: expose capability step updates over rpc"
```
---
### Task 3: MCP Update Tool And Creation Parity
**Files:**
- Modify: `src/wf_mcp/workflow_surface/models.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Modify: `src/wf_mcp/proxy/runtime.py`
- Modify: `tests/wf_mcp/workflow_surface/test_drafts.py`
- Modify: `tests/wf_mcp/server/test_tools.py`
- Modify: `tests/wf_mcp/server/test_config.py`
**Interfaces:**
- Consumes: Task 2's public update method and Task 1's model.
- Produces: `wf.workflow.update_capability_step`.
- Extends: `wf.workflow.add_step_from_capability`.
- [ ] **Step 1: Write failing MCP request tests**
Add:
```python
request = UpdateCapabilityStepRequest.model_validate(
{
"workspace_id": "report",
"revision": 4,
"step_id": "publish",
"update": {
"desc": None,
"input": [
{"value": "markdown", "target": "request.format"},
],
},
}
)
assert request.update.model_fields_set == {"desc", "input"}
assert isinstance(request.update.input[0], InputValueBinding)
```
Add malformed update cases and add-capability request parity tests.
- [ ] **Step 2: Write failing discovery and real invocation tests**
Require the tool in normal and search-mode inventories. Call it through an
in-memory FastMCP client and assert:
- one delegation;
- typed `CapabilityStepUpdate`;
- exact `model_fields_set`;
- exact canonical binding order.
Extend the add-capability invocation test with metadata and a literal binding.
- [ ] **Step 3: Implement MCP request and tools**
Add:
```python
class UpdateCapabilityStepRequest(BaseModel):
workspace_id: WorkspaceId
revision: int = Field(ge=1)
step_id: NonEmptyString
update: CapabilityStepUpdate
```
Register:
```python
@server.tool(
name="wf.workflow.update_capability_step",
title="Update Capability Step",
description=(
"Update capability-step metadata and optionally replace its complete "
"canonical input bindings. Preserves use, routes, and outputs."
),
)
async def update_capability_step(
request: UpdateCapabilityStepRequest,
) -> DraftWorkspaceResult:
return DraftWorkspaceResult.model_validate(
await handlers.update_capability_step(
workspace_id=request.workspace_id,
revision=request.revision,
step_id=request.step_id,
update=request.update,
)
)
```
Add the name to `_SEARCH_ALWAYS_VISIBLE_TOOL_NAMES`. Extend
`AddStepFromCapabilityRequest` and its tool delegation with the creation-parity
fields.
- [ ] **Step 4: Verify and commit Task 3**
Run:
```powershell
.venv\Scripts\python.exe -m pytest -n 0 `
tests/wf_mcp/workflow_surface/test_drafts.py `
tests/wf_mcp/server/test_tools.py `
tests/wf_mcp/server/test_config.py `
-q --basetemp=.pytest-capability-update-task3
.venv\Scripts\ruff.exe check src/wf_mcp tests/wf_mcp
.venv\Scripts\basedpyright.exe --level error
```
Commit:
```bash
git add src/wf_mcp tests/wf_mcp
git commit -m "feat: expose capability step updates to mcp"
```
---
### Task 4: Add And Update CLI
**Files:**
- Create: `src/wf_cli/commands/draft_update.py`
- Modify: `src/wf_cli/commands/draft_add.py`
- Modify: `src/wf_cli/commands/draft_options.py`
- Modify: `src/wf_cli/commands/drafts.py`
- Modify: `tests/wf_cli/test_app.py`
- Modify: `tests/wf_cli/test_remote_target.py`
**Interfaces:**
- Consumes: Task 2's local/remote update and extended add methods.
- Produces: `wf draft update capability`.
- Extends: `wf draft add capability`.
- [ ] **Step 1: Write failing parser tests**
Refactor the private input-shaped path parser so the option name and target
audience are parameters:
```python
def _parse_input_path_binding_flags(
values: list[str] | None,
*,
option_name: str,
target_label: str,
) -> list[InputPathBinding]:
```
Keep `parse_step_input_binding_flags` behavior unchanged. Add:
```python
def parse_capability_input_binding_flags(
values: list[str] | None,
) -> list[InputPathBinding]:
return _parse_input_path_binding_flags(
values,
option_name="--input",
target_label="node-local",
)
```
Reuse `parse_step_input_value_flags` and `parse_step_input_bindings_file`.
Write tests for `--input`-specific diagnostics and canonical order.
- [ ] **Step 2: Write failing update command tests**
Add local CLI tests for:
```bash
wf draft update capability report --revision 4 --step publish \
--description "Publish report" \
--retry 0 \
--clear-timeout \
--input state.report.title=request.title \
--value request.format='"markdown"'
```
Assert the fake handler receives a `CapabilityStepUpdate` whose
`model_fields_set` is exactly:
```python
{"desc", "retry", "timeout_seconds", "input"}
```
and whose input bindings preserve path-then-literal order.
Add tests for every set/clear conflict, bindings-file conflict, clear-input
conflict, empty update, and validation before `load_cli_context`.
- [ ] **Step 3: Implement the update command group**
Create `draft_update.py` with a Typer group and `capability` command. Build a
plain `payload` dictionary only from selected flags:
```python
payload: dict[str, object] = {}
if description is not None:
payload["desc"] = description
elif clear_description:
payload["desc"] = None
if retry is not None:
payload["retry"] = retry
elif clear_retry:
payload["retry"] = None
if timeout_seconds is not None:
payload["timeout_seconds"] = timeout_seconds
elif clear_timeout:
payload["timeout_seconds"] = None
if input_mode_selected:
payload["input"] = bindings
update = CapabilityStepUpdate.model_validate(payload)
```
Parse all exclusivity and validation errors before loading context. Register:
```python
app.add_typer(draft_update.app, name="update")
```
in `drafts.py`.
- [ ] **Step 4: Write failing add-command parity tests**
Test `wf draft add capability` with metadata, `--input`, `--value`, and
`--bindings-file`. Pin:
- canonical path/value order;
- `retry=0`;
- bindings-file exact interleaving;
- file/convenience exclusivity before context;
- existing path-only invocation unchanged.
- [ ] **Step 5: Extend add capability**
Add the approved flags and call canonical `input_bindings`. Do not pass both
the compatibility map and canonical list. The CLI should use canonical
bindings for every new invocation; `input_map` remains only for non-CLI
compatibility callers.
- [ ] **Step 6: Add remote parity tests**
Use the real ASGI RPC target to assert exact methods and payloads:
```text
workflow.draft_workspaces.update_capability_step
workflow.draft_workspaces.add_step_from_capability
```
For update, assert omitted metadata keys do not appear and explicit clears do.
For add, assert ordered path/value input bindings and metadata.
- [ ] **Step 7: Verify and commit Task 4**
Run:
```powershell
.venv\Scripts\python.exe -m pytest -n 0 `
tests/wf_cli/test_app.py `
tests/wf_cli/test_remote_target.py `
-q --basetemp=.pytest-capability-update-task4
.venv\Scripts\ruff.exe check src/wf_cli tests/wf_cli
.venv\Scripts\basedpyright.exe --level error
```
Commit:
```bash
git add src/wf_cli tests/wf_cli
git commit -m "feat: add capability step update cli"
```
---
### Task 5: Documentation, Verification, And Review
**Files:**
- Modify: `ISSUES.md`
- Modify: `docs/wf_cli.md`
- Modify: `docs/workflow_drafts.md`
- Modify: `docs/workflow_capabilities.md`
- Modify: `docs/wf_mcp_operator_manual.md`
- Modify: `skills/wf-cli/SKILL.md`
- Modify: `skills/wf-workflow/references/draft-workspaces.md`
- Modify: `skills/wf-workflow/references/workflow-lifecycle.md`
- Modify: `docs/current_roadmap.md`
- Move:
`docs/superpowers/plans/2026-07-26-capability-step-update.md` to
`docs/historical/superpowers/plans/2026-07-26-capability-step-update.md`
**Interfaces:**
- Consumes: verified Tasks 1-4.
- Produces: accurate current docs, closed issue state, archived checked plan,
and final review evidence.
- [ ] **Step 1: Update issue and roadmap state**
Mark the focused capability-step update issue complete. State that:
- creation accepts metadata and canonical literal inputs;
- update preserves `use`, routes, and outputs;
- update can atomically replace canonical input bindings;
- changing capability remains remove/add;
- TypeScript parity remains open.
Add a completed roadmap entry linked to the historical plan.
- [ ] **Step 2: Update user and agent docs**
Document:
```bash
wf draft add capability report --revision 3 --step publish \
--capability local.report.publish \
--description "Publish report" \
--retry 2 \
--timeout-seconds 30 \
--input state.report.title=request.title \
--value request.format='"markdown"' \
--route ok=__end__
wf draft update capability report --revision 4 --step publish \
--clear-description \
--retry 0 \
--clear-timeout
```
Explain omission versus clearing, complete input replacement, bindings-file
interleaving, and separate route/output operations. Add JSON-RPC and MCP
operation names to live inventories.
- [ ] **Step 3: Archive the checked plan**
Mark every checkbox complete, move the plan to the historical path, and update
all live links:
```powershell
rg -n '2026-07-26-capability-step-update' docs skills README.md
```
- [ ] **Step 4: Run the full focused matrix**
Run:
```powershell
.venv\Scripts\python.exe -m pytest -n 0 `
tests/wf_api/test_drafts_service.py `
tests/wf_transport_rpc_http/test_app.py `
tests/wf_transport_rpc_http/test_client.py `
tests/wf_mcp/workflow_surface/test_drafts.py `
tests/wf_mcp/server/test_tools.py `
tests/wf_mcp/server/test_config.py `
tests/wf_cli/test_app.py `
tests/wf_cli/test_remote_target.py `
-q --basetemp=.pytest-capability-update-final
```
Expected: PASS with only already-known dependency deprecation warnings.
- [ ] **Step 5: Run static verification**
Run:
```powershell
.venv\Scripts\ruff.exe check
.venv\Scripts\ruff.exe format --check
.venv\Scripts\basedpyright.exe --level error
git diff --check
```
Expected: all clean.
- [ ] **Step 6: Run independent two-axis review**
Review from commit `05afc2e1` through the current worktree against:
```text
docs/superpowers/specs/2026-07-26-capability-step-update-design.md
```
Require findings first with severity and file/line references. The spec
reviewer must inspect:
- omission versus explicit null;
- explicit `input:null` rejection;
- stale-revision precedence;
- metadata-only operation without capability resolution;
- no-op revision behavior;
- preservation of `use`, outputs, and routes;
- shared input preflight rather than duplicated semantics;
- add/update canonical input parity;
- RPC `exclude_unset=True`;
- CLI pre-context exclusivity;
- MCP discovery and real invocation;
- docs and issue truthfulness.
Fix every Critical and Important finding and rerun affected checks.
- [ ] **Step 7: Commit documentation and review fixes**
Commit documentation:
```bash
git add ISSUES.md docs skills
git commit -m "docs: complete capability step updates"
```
Commit production/test review fixes separately after verification. Finish:
```powershell
git status --short --branch
git log --oneline -10
```
Expected: clean worktree and task-level commits.