Files
lda-wf/docs/historical/superpowers/plans/2026-06-03-remote-cli-lifecycle-rpc-methods.md
T

566 lines
16 KiB
Markdown

# Remote CLI Lifecycle RPC Methods 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:** Make the `wf` draft/artifact/deploy commands work against `client.target.kind = "rpc_http"` instead of failing fast as local-only commands.
**Architecture:** Extend the fixed JSON-RPC method set, then extend `RpcWorkflowApiClient` to structurally support every `WorkflowApi` method used by the CLI. Only after the client and server both support the methods should the CLI command modules switch from local-only context to target-aware context. Keep this as remote workflow lifecycle support, not source registry/auth/MCP hosting.
**Tech Stack:** Python 3.14, Pydantic v2, fastapi-jsonrpc, httpx, Typer, pytest, ruff, basedpyright.
---
## Scope
In scope:
- Add missing JSON-RPC methods for draft workspace, artifact, and deployment operations used by CLI.
- Add matching `RpcWorkflowApiClient` methods.
- Route `wf draft`, `wf artifact`, and `wf deploy` through `load_cli_context_from_typer`.
- Add remote CLI integration tests for create draft → validate → save artifact → save deployment → validate deployment.
- Keep existing local CLI behavior and old `wf_mcp.config.json` compatibility.
Out of scope:
- Source registry.
- MCP/OpenAPI source config.
- `/mcp` hosting.
- Auth.
- SQL stores.
- Remote docs/schema/explain commands.
- Streaming/progress.
---
## Required Method Coverage
The client must implement every method used by these command modules:
```text
src/wf_cli/commands/drafts.py
list_draft_workspaces
get_draft_workspace
create_draft_workspace_from_capability
patch_draft_workspace
validate_draft_workspace
create_wrapper_from_workspace
create_artifact_from_workspace
src/wf_cli/commands/artifacts.py
list_artifacts
inspect_artifact
src/wf_cli/commands/deployments.py
validate_deployment
list_deployments
inspect_deployment
save_deployment
delete_deployment
```
Do not route a command module to target-aware context until the client has the
methods that module calls.
---
## Task 1: Add Missing RPC DTOs and Server Methods for Artifacts/Deployments
**Files:**
- Modify: `src/wf_transport_rpc_http/models.py`
- Modify: `src/wf_transport_rpc_http/app.py`
- Modify: `src/wf_transport_rpc_http/__init__.py`
- Modify: `tests/wf_transport_rpc_http/test_app.py`
- [ ] **Step 1: Add focused RPC app tests**
Append a test that seeds an artifact/deployment through `server.api`, then calls:
```text
workflow.artifacts.list
workflow.artifacts.inspect
workflow.deployments.list
workflow.deployments.inspect
workflow.deployments.delete
```
Assertions:
```python
assert listed_artifacts["result"]["nodes"]
assert inspected_artifact["result"]["artifact_id"] == "rpc_lifecycle"
assert listed_deployments["result"]["deployments"]
assert inspected_deployment["result"]["deployment_id"] == "rpc_lifecycle.default"
assert deleted["result"]["deployment_id"] == "rpc_lifecycle.default"
```
Use the existing `_constant_plan()` helper in `tests/wf_transport_rpc_http/test_app.py` if possible. Do not bypass the RPC app for the operations under test.
- [ ] **Step 2: Run the new test and verify method-not-found failures**
Run:
```bash
uv run pytest tests/wf_transport_rpc_http/test_app.py::test_rpc_artifact_and_deployment_catalog_methods -q
```
Expected: fail with JSON-RPC method-not-found for the newly required methods.
- [ ] **Step 3: Add DTOs**
In `src/wf_transport_rpc_http/models.py`, add:
```python
class ListArtifactsParams(RpcParamsModel):
query: str | None = None
kind: str | None = None
cursor: str | None = None
limit: int = Field(default=50, ge=1, le=100)
class InspectArtifactParams(RpcParamsModel):
artifact_id: str = Field(min_length=1)
version: int = Field(ge=1)
class ListDeploymentsParams(RpcParamsModel):
pass
class InspectDeploymentParams(RpcParamsModel):
deployment_id: str = Field(min_length=1)
class DeleteDeploymentParams(RpcParamsModel):
deployment_id: str = Field(min_length=1)
```
Export these from `src/wf_transport_rpc_http/__init__.py`.
- [ ] **Step 4: Register artifact/deployment methods**
In `src/wf_transport_rpc_http/app.py`, import the new DTOs and register:
```text
workflow.artifacts.list -> server.api.list_artifacts
workflow.artifacts.inspect -> server.api.inspect_artifact
workflow.deployments.list -> server.api.list_deployments
workflow.deployments.inspect -> server.api.inspect_deployment
workflow.deployments.delete -> server.api.delete_deployment
```
Use the same expected-error handling as existing methods:
```python
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
raise_workflow_rpc_error(exc)
```
For `ListArtifactsParams.kind`, pass the string through as `kind=params.kind`.
If basedpyright complains because `WorkflowApi.list_artifacts` expects
`ArtifactKind | None`, narrow with:
```python
kind = params.kind if params.kind in {"workflow", "wrapper"} else None
```
and reject invalid values in the DTO if needed.
- [ ] **Step 5: Run focused transport tests**
Run:
```bash
uv run pytest tests/wf_transport_rpc_http/test_app.py -q
uv run ruff check src/wf_transport_rpc_http tests/wf_transport_rpc_http
uv run basedpyright --level error src/wf_transport_rpc_http tests/wf_transport_rpc_http
```
Expected: tests pass, ruff clean, basedpyright 0 errors.
---
## Task 2: Add Draft Workspace RPC Methods
**Files:**
- Modify: `src/wf_transport_rpc_http/models.py`
- Modify: `src/wf_transport_rpc_http/app.py`
- Modify: `src/wf_transport_rpc_http/__init__.py`
- Modify: `tests/wf_transport_rpc_http/test_app.py`
- [ ] **Step 1: Add focused draft workspace RPC test**
Append a test that calls:
```text
workflow.draft_workspaces.create_from_capability
workflow.draft_workspaces.list
workflow.draft_workspaces.get
workflow.draft_workspaces.validate
workflow.draft_workspaces.patch
workflow.draft_workspaces.create_artifact
workflow.draft_workspaces.create_wrapper
```
Use a small patch such as changing the draft name or title through the existing
workspace patch format. Assert:
```python
assert created["result"]["workspace_id"] == "remote_ws"
assert listed["result"]["workspaces"]
assert fetched["result"]["workspace_id"] == "remote_ws"
assert validated["result"]["status"] in {"valid", "invalid"}
assert patched["result"]["revision"] == created["result"]["revision"] + 1
assert artifact["result"]["artifact_id"] == "remote_artifact"
assert wrapper["result"]["artifact_id"] == "remote_wrapper"
```
If wrapper creation requires an output-capable draft and the simple capability
draft cannot satisfy it, keep `create_wrapper` covered by client method tests
and document why the RPC app integration test only covers `create_artifact`.
- [ ] **Step 2: Run the new test and verify method-not-found failures**
Run:
```bash
uv run pytest tests/wf_transport_rpc_http/test_app.py::test_rpc_draft_workspace_methods -q
```
Expected: fail with method-not-found for missing workspace methods.
- [ ] **Step 3: Add DTOs**
Add to `src/wf_transport_rpc_http/models.py`:
```python
class ListDraftWorkspacesParams(RpcParamsModel):
pass
class GetDraftWorkspaceParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
include_draft: bool = False
class PatchDraftWorkspaceParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
revision: int = Field(ge=1)
patch: list[dict[str, Any]]
class ValidateDraftWorkspaceParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
class CreateArtifactFromWorkspaceParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
artifact_id: str = Field(min_length=1)
version: int = Field(ge=1)
title: str = Field(min_length=1)
outcomes: list[str]
kind: str = "workflow"
description: str | None = None
required_capabilities: dict[str, dict[str, Any]] | None = None
source_bindings: dict[str, str] | None = None
created_from_catalog_version: str | None = None
class CreateWrapperFromWorkspaceParams(RpcParamsModel):
workspace_id: str = Field(min_length=1)
artifact_id: str = Field(min_length=1)
version: int = Field(ge=1)
title: str = Field(min_length=1)
outcomes: list[str]
description: str | None = None
required_capabilities: dict[str, dict[str, Any]] | None = None
source_bindings: dict[str, str] | None = None
created_from_catalog_version: str | None = None
```
Export these from `src/wf_transport_rpc_http/__init__.py`.
- [ ] **Step 4: Register draft workspace methods**
In `src/wf_transport_rpc_http/app.py`, register:
```text
workflow.draft_workspaces.list
workflow.draft_workspaces.get
workflow.draft_workspaces.create_from_capability
workflow.draft_workspaces.patch
workflow.draft_workspaces.validate
workflow.draft_workspaces.create_artifact
workflow.draft_workspaces.create_wrapper
```
Map them to the matching `server.api` methods:
```python
server.api.list_draft_workspaces()
server.api.get_draft_workspace(...)
server.api.create_draft_workspace_from_capability(...)
server.api.patch_draft_workspace(...)
server.api.validate_draft_workspace(...)
server.api.create_artifact_from_workspace(...)
server.api.create_wrapper_from_workspace(...)
```
The existing `workflow.drafts.create_from_capability` method may stay for
backward compatibility. It can call the same API method as
`workflow.draft_workspaces.create_from_capability`.
- [ ] **Step 5: Run focused transport tests**
Run:
```bash
uv run pytest tests/wf_transport_rpc_http/test_app.py -q
uv run ruff check src/wf_transport_rpc_http tests/wf_transport_rpc_http
uv run basedpyright --level error src/wf_transport_rpc_http tests/wf_transport_rpc_http
```
Expected: tests pass, ruff clean, basedpyright 0 errors.
---
## Task 3: Extend RpcWorkflowApiClient
**Files:**
- Modify: `src/wf_transport_rpc_http/client.py`
- Modify: `tests/wf_transport_rpc_http/test_client.py`
- [ ] **Step 1: Add client tests for the newly exposed methods**
Add tests covering:
```text
list_artifacts / inspect_artifact
list_deployments / inspect_deployment / validate_deployment / delete_deployment
list_draft_workspaces / get_draft_workspace / validate_draft_workspace
create_draft_workspace_from_capability
patch_draft_workspace
create_artifact_from_workspace
create_wrapper_from_workspace if feasible
```
Use `httpx.ASGITransport(app=create_rpc_app(server))` like the existing client
tests. Seed artifacts/deployments through `server.api` where the method under
test is only read/list/inspect. For create/patch workspace tests, call through
the client.
- [ ] **Step 2: Run client tests and verify failures**
Run:
```bash
uv run pytest tests/wf_transport_rpc_http/test_client.py -q
```
Expected: fail because `RpcWorkflowApiClient` lacks the new methods.
- [ ] **Step 3: Implement client methods**
Add one client method for each CLI-used `WorkflowApi` method listed in
"Required Method Coverage".
Wire methods to the JSON-RPC names from Tasks 1-2. Examples:
```python
async def list_artifacts(...):
return await self._call("workflow.artifacts.list", {...})
async def get_draft_workspace(...):
return await self._call("workflow.draft_workspaces.get", {...})
async def create_artifact_from_workspace(...):
return await self._call("workflow.draft_workspaces.create_artifact", {...})
```
Keep signatures close to `WorkflowApi` so basedpyright accepts CLI command
calls without casts.
- [ ] **Step 4: Run client and type checks**
Run:
```bash
uv run pytest tests/wf_transport_rpc_http/test_client.py -q
uv run basedpyright --level error src/wf_transport_rpc_http src/wf_cli
```
Expected: client tests pass and basedpyright reports 0 errors.
---
## Task 4: Route Draft/Artifact/Deploy CLI Through Target-Aware Context
**Files:**
- Modify: `src/wf_cli/commands/drafts.py`
- Modify: `src/wf_cli/commands/artifacts.py`
- Modify: `src/wf_cli/commands/deployments.py`
- Modify: `tests/wf_cli/test_remote_target.py`
- [ ] **Step 1: Write remote CLI lifecycle test**
Add a test that starts an in-process JSON-RPC app and invokes Typer commands
with:
```text
wf --config wf.json --url http://test/rpc draft create-from-capability ...
wf --config wf.json --url http://test/rpc draft validate ...
wf --config wf.json --url http://test/rpc draft save ...
wf --config wf.json --url http://test/rpc artifact inspect ...
wf --config wf.json --url http://test/rpc deploy save ...
wf --config wf.json --url http://test/rpc deploy validate ...
```
Use the same `httpx.AsyncClient` monkeypatch pattern already present in
`tests/wf_cli/test_remote_target.py`.
Assertions:
```python
assert created.exit_code == 0
assert validated.exit_code == 0
assert saved_artifact.exit_code == 0
assert inspected_artifact.exit_code == 0
assert saved_deployment.exit_code == 0
assert validated_deployment.exit_code == 0
```
Also assert key output fragments:
```python
assert '"workspace_id": "remote_ws"' in created.output
assert '"status": "valid"' in validated.output
assert '"artifact_id": "remote_artifact"' in saved_artifact.output
assert '"deployment_id": "remote_artifact.default"' in saved_deployment.output
assert '"status": "runnable"' in validated_deployment.output
```
- [ ] **Step 2: Run test and verify failure**
Run:
```bash
uv run pytest tests/wf_cli/test_remote_target.py::test_wf_remote_draft_artifact_deploy_lifecycle -q
```
Expected: fail because command modules still use local-only context.
- [ ] **Step 3: Switch command imports**
In `src/wf_cli/commands/drafts.py`, `artifacts.py`, and `deployments.py`,
replace:
```python
from wf_cli.context import load_local_cli_context_from_typer as load_cli_context
```
with:
```python
from wf_cli.context import load_cli_context_from_typer as load_cli_context
```
Keep the local `load_cli_context` alias name so existing tests that monkeypatch
`wf_cli.commands.<module>.load_cli_context` keep working.
- [ ] **Step 4: Run CLI tests**
Run:
```bash
uv run pytest tests/wf_cli/test_remote_target.py tests/wf_cli/test_run_deploy.py tests/wf_cli/test_discovery_lifecycle.py -q
uv run basedpyright --level error src/wf_cli src/wf_transport_rpc_http
```
Expected: tests pass and basedpyright reports 0 errors.
---
## Task 5: Documentation and Verification
**Files:**
- Modify: `docs/superpowers/specs/2026-06-03-workflow-config-targets-and-sources.md`
- Modify: `docs/current_roadmap.md`
- [ ] **Step 1: Update spec status**
Update the implementation status to say:
```markdown
- remote JSON-RPC client support now covers capability, draft workspace,
artifact, deployment, and run CLI commands
- draft/artifact/deploy commands no longer fail fast for `rpc_http` targets
```
Remove or update the older local-only/fail-fast status line.
- [ ] **Step 2: Update roadmap**
Update the roadmap note to say the basic remote CLI lifecycle is wired:
```markdown
selected `wf` commands can target JSON-RPC HTTP
```
should become:
```markdown
the basic `wf` lifecycle can target JSON-RPC HTTP: capability discovery,
draft workspace authoring, artifact/deployment operations, run, inspect, and
bounded trace.
```
- [ ] **Step 3: Run focused verification**
Run:
```bash
uv run pytest tests/wf_transport_rpc_http tests/wf_cli/test_remote_target.py tests/wf_cli/test_run_deploy.py tests/wf_cli/test_discovery_lifecycle.py -q
uv run ruff check src/wf_transport_rpc_http src/wf_cli tests/wf_transport_rpc_http tests/wf_cli
uv run ruff format --check src/wf_transport_rpc_http src/wf_cli tests/wf_transport_rpc_http tests/wf_cli
uv run basedpyright --level error src/wf_transport_rpc_http src/wf_cli tests/wf_transport_rpc_http tests/wf_cli
```
Expected: pass, except basedpyright may still exit non-zero for the known
workspace enumeration warning only when run at full workspace scope. For the
focused command above, expect 0 errors.
- [ ] **Step 4: Run full verification**
Run:
```bash
uv run pytest -q
uv run ruff check
uv run ruff format --check
uv run basedpyright --level error
```
Expected:
- pytest passes with current skip/xfail count
- ruff passes
- basedpyright reports `0 errors`; if it exits 1 only due to workspace
enumeration warning, report that exactly
---
## Self-Review Notes
This plan intentionally adds server RPC methods before client methods and client
methods before CLI routing. That sequence prevents the previous half-migration
problem where commands could receive a partial client.
Do not remove old local tests or monkeypatch seams. The command modules can keep
a local import alias named `load_cli_context` for compatibility with existing
tests, but the imported helper should become target-aware after Task 4.