docs: plan semantic draft authoring

This commit is contained in:
lda
2026-06-27 14:08:17 +07:00 Verified
parent 4987409473
commit 4b03f54a1d
3 changed files with 1121 additions and 0 deletions
@@ -0,0 +1,311 @@
# Canonical TOML Path Strings 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 canonical TOML-key strings the advertised and serialized representation for workflow paths while retaining structured-object input compatibility for existing stored data.
**Architecture:** Keep `GraphSourcePath`, `StatePath`, and `LocalPath` as structured immutable values. Move TOML-key parsing into `wf_core.paths`, add one canonical formatter there, and make every Pydantic path serializer emit strings. `wf_authoring` delegates to the core parser instead of owning a second grammar.
**Tech Stack:** Python 3.14, stdlib `tomllib`, Pydantic v2 core schemas, pytest, Ruff, basedpyright.
---
### Task 1: Define The Core TOML-Key Grammar
**Files:**
- Modify: `src/wf_core/paths.py`
- Test: `tests/core/test_path_values.py`
- [ ] **Step 1: Write failing parser and formatter tests**
Add tests proving quoted segments round-trip and roots remain typed:
```python
def test_toml_path_strings_round_trip_literal_segments() -> None:
source = GraphSourcePath.parse('input."customer.name"."display name"')
target = StatePath.parse('state."report.title"')
local = LocalPath.parse('payload."raw.value"')
assert source.parts == ("customer.name", "display name")
assert target.parts == ("report.title",)
assert local.parts == ("payload", "raw.value")
assert str(source) == 'input."customer.name"."display name"'
assert str(target) == 'state."report.title"'
assert str(local) == 'payload."raw.value"'
```
Also test `LocalPath.parse(".")`, bare keys, malformed TOML, invalid roots, and empty writable state paths.
- [ ] **Step 2: Run the focused test and verify RED**
Run: `uv run pytest tests/core/test_path_values.py -q`
Expected: quoted full paths fail because current core parsing uses `str.split(".")`.
- [ ] **Step 3: Implement shared parsing and formatting**
In `src/wf_core/paths.py`, add the shared implementation:
```python
_BARE_TOML_KEY = re.compile(r"^[A-Za-z0-9_-]+$")
def parse_toml_path_segments(expr: str) -> tuple[str, ...]:
"""Parse a TOML key expression into literal path segments."""
try:
parsed = tomllib.loads(f"{expr} = true")
except tomllib.TOMLDecodeError as exc:
raise PathResolutionError(
f"invalid TOML path {expr!r}; quote path segments containing dots or spaces"
) from exc
parts: list[str] = []
current: object = parsed
while isinstance(current, dict):
if len(current) != 1:
raise PathResolutionError(f"invalid TOML path {expr!r}")
key, current = next(iter(current.items()))
parts.append(_validate_segment(key, path_kind="TOML path"))
if current is not True or not parts:
raise PathResolutionError(f"invalid TOML path {expr!r}")
return tuple(parts)
def format_toml_path_segments(parts: tuple[str, ...]) -> str:
"""Format literal segments as one canonical TOML key expression."""
if not parts:
raise PathResolutionError("cannot format an empty TOML path")
return ".".join(
part
if _BARE_TOML_KEY.fullmatch(part)
else json.dumps(part, ensure_ascii=False)
for part in parts
)
```
Use `tomllib.loads(f"{expr} = true")` for parsing. The formatter emits bare
TOML keys when legal and quoted TOML basic strings otherwise. Keep the local
root marker `.` as a special complete path, not a TOML key expression.
Update:
```python
GraphSourcePath.parse(raw)
GraphSourcePath.__str__()
StatePath.parse(raw)
StatePath.__str__()
LocalPath.parse(raw)
LocalPath.__str__()
```
`GraphSourcePath.parse` parses the whole expression, then treats the first
segment as the graph root. `StatePath` requires root `state` plus at least one
remaining segment. `LocalPath` has no serialized `local.` prefix.
- [ ] **Step 4: Run focused core tests**
Run: `uv run pytest tests/core/test_path_values.py tests/core/test_nested_state_paths.py -q`
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add src/wf_core/paths.py tests/core/test_path_values.py
git commit -m "feat: define canonical TOML workflow paths"
```
### Task 2: Serialize Paths As Strings And Advertise Strings In JSON Schema
**Files:**
- Modify: `src/wf_core/paths.py`
- Modify: `tests/core/test_path_values.py`
- Modify: `tests/core/test_canonical_node_bindings.py`
- [ ] **Step 1: Write failing Pydantic projection tests**
Extend the existing Pydantic path payload test:
```python
def test_path_models_serialize_strings_but_accept_structural_compat() -> None:
payload = PathPayload.model_validate(
{
"source": {"root": "input", "parts": ["user.name"]},
"target": {"root": "state", "parts": ["person name"]},
"local": {"root": "local", "parts": ["payload.text"]},
}
)
assert payload.model_dump(mode="json") == {
"source": 'input."user.name"',
"target": 'state."person name"',
"local": '"payload.text"',
}
assert PathPayload.model_json_schema()["properties"]["source"]["type"] == "string"
```
- [ ] **Step 2: Run tests and verify RED**
Run: `uv run pytest tests/core/test_path_values.py tests/core/test_canonical_node_bindings.py -q`
Expected: current serializers emit `{root, parts}` objects.
- [ ] **Step 3: Change serializers and JSON schemas**
Make each path `_serialize` return `str(value)`. Replace the structural
`_path_json_schema` with a string schema whose description documents TOML-key
quoting. Do not include the compatibility object in generated JSON Schema;
validators continue accepting it so old persisted records remain readable.
Keep a comment at the validator seam:
```python
# Structural objects remain input-only compatibility for persisted records.
# New schemas and serializers expose the canonical TOML-key string form.
```
- [ ] **Step 4: Update exact serialized binding expectations**
Adjust tests that assert JSON payloads to expect strings such as
`input.message`, `state.echoed`, and `.`. Keep field-level assertions rather
than replacing whole large snapshots.
- [ ] **Step 5: Run core serialization tests**
Run: `uv run pytest tests/core/test_path_values.py tests/core/test_canonical_node_bindings.py tests/core/test_run_codec.py -q`
Expected: PASS, including decoding old structural path objects.
- [ ] **Step 6: Commit**
```bash
git add src/wf_core/paths.py tests/core/test_path_values.py tests/core/test_canonical_node_bindings.py
git commit -m "feat: serialize canonical workflow path strings"
```
### Task 3: Remove The Duplicate Authoring Parser
**Files:**
- Modify: `src/wf_authoring/dsl/path_inputs.py`
- Modify: `tests/authoring/test_path_inputs.py`
- [ ] **Step 1: Add delegation coverage**
Add a test proving full rooted strings and explicit-root expressions resolve to
the same structured value:
```python
def test_authoring_paths_share_core_toml_grammar() -> None:
assert coerce_graph_path('state."person.name"') == GraphSourcePath(
"state", ("person.name",)
)
assert coerce_graph_path('"person.name"', root="state") == GraphSourcePath(
"state", ("person.name",)
)
```
- [ ] **Step 2: Replace `_parse_toml_key_expr`**
Delete the local `tomllib` parser and import
`parse_toml_path_segments` from `wf_core.paths`. Preserve iterable and
structural-object coercion behavior for Python callers.
- [ ] **Step 3: Run authoring tests**
Run: `uv run pytest tests/authoring/test_path_inputs.py tests/authoring/test_builder.py tests/authoring/test_conditions.py -q`
Expected: PASS.
- [ ] **Step 4: Commit**
```bash
git add src/wf_authoring/dsl/path_inputs.py tests/authoring/test_path_inputs.py
git commit -m "refactor: share workflow path grammar"
```
### Task 4: Update Draft, Transport, And Compatibility Coverage
**Files:**
- Modify: `tests/wf_api/test_drafts_service.py`
- Modify: `tests/wf_transport_rpc_http/test_app.py`
- Modify: `tests/wf_transport_rpc_http/test_client.py`
- Modify: `tests/wf_mcp/workflow_surface/test_drafts.py`
- [ ] **Step 1: Add one stored-draft compatibility regression**
Create a workspace from a draft containing structural path objects, retrieve or
patch it, and assert the next serialized draft uses canonical strings while
preserving the same path values.
- [ ] **Step 2: Update focused transport assertions**
Change only assertions for serialized path fields. RPC and MCP requests should
advertise and return strings; input model tests must still accept structural
objects.
- [ ] **Step 3: Run affected suites**
Run:
```bash
uv run pytest 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 -q
```
Expected: PASS.
- [ ] **Step 4: Commit**
```bash
git add 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
git commit -m "test: cover canonical path transport compatibility"
```
### Task 5: Documentation And Final Verification
**Files:**
- Modify: `docs/workflow_drafts.md`
- Modify: `docs/wf_authoring_control_flow.md`
- Modify: `skills/wf-cli/SKILL.md`
- Modify: `skills/wf-workflow/references/draft-workspaces.md`
- Modify: `skills/wf-workflow/references/direct-plan-import.md`
- Modify: `docs/current_roadmap.md`
- Modify: `docs/superpowers/specs/2026-06-27-draft-semantic-authoring-boundary.md`
- Move after completion: `docs/superpowers/plans/2026-06-27-canonical-toml-path-strings.md` -> `docs/historical/superpowers/plans/2026-06-27-canonical-toml-path-strings.md`
- [ ] **Step 1: Replace structural path examples**
Use canonical examples:
```json
{"path":"input.text","target":"text"}
{"source":"result","target":"state.result"}
```
Document TOML quoting with `state."field.with.dot"`. State that structural
objects are input-only compatibility and must not be generated by agents.
- [ ] **Step 2: Record implementation status**
Mark the canonical-path section implemented in the design spec and add a short
completed roadmap entry.
- [ ] **Step 3: Run verification**
```bash
uv run pytest tests/core/test_path_values.py tests/core/test_canonical_node_bindings.py tests/authoring/test_path_inputs.py tests/authoring/test_builder.py 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 -q
uv run ruff check
uv run ruff format --check
uv run basedpyright --level error
git diff --check
```
Expected: all tests pass, Ruff is clean, basedpyright reports zero errors, and
`git diff --check` reports no whitespace errors.
- [ ] **Step 4: Archive and commit the plan**
```bash
git mv docs/superpowers/plans/2026-06-27-canonical-toml-path-strings.md docs/historical/superpowers/plans/2026-06-27-canonical-toml-path-strings.md
git add docs skills
git commit -m "docs: document canonical workflow path strings"
```
@@ -0,0 +1,448 @@
# Draft Semantic Authoring And Compile 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:** Separate semantic draft authoring from workspace lifecycle, add `branch` and `handle`, make capability-step routing complete, and expose read-only stored-workspace compilation.
**Architecture:** `WorkflowDraft` remains the only stored authoring document. A new `WorkflowDraftAuthoringApi` resolves capability metadata and lowers semantic intent to one atomic patch through `WorkflowDraftApi`; the latter retains lifecycle, validation, compilation, low-level edits, and JSON Patch. Public CLI, RPC, and MCP surfaces remain unified through `WorkflowApi`.
**Tech Stack:** Python 3.14, Pydantic v2, JSON Patch, Typer, JSON-RPC, FastMCP, pytest, Ruff, basedpyright.
**Prerequisite:** Complete `docs/superpowers/plans/2026-06-27-canonical-toml-path-strings.md` first.
---
### Task 1: Extract The Semantic Authoring Service Without Behavior Changes
**Files:**
- Create: `src/wf_api/draft_authoring.py`
- Create: `src/wf_api/draft_payloads.py`
- Modify: `src/wf_api/drafts.py`
- Modify: `src/wf_api/service.py`
- Test: `tests/wf_api/test_drafts_service.py`
- [ ] **Step 1: Add a facade delegation test**
Add a focused test asserting `WorkflowApi` constructs a sibling authoring
service and existing `bind_output_to_state` behavior still consumes one
revision.
- [ ] **Step 2: Extract shared draft payload helpers**
Move the existing `_draft_step`, `_escape_json_pointer`,
`_draft_input_bindings_payload`, `_draft_output_bindings_payload`, and
`_state_root_field` bodies unchanged into `src/wf_api/draft_payloads.py`.
Rename them to `draft_step`, `escape_json_pointer`, `input_bindings_payload`,
`output_bindings_payload`, and `state_root_field`, update both service imports,
and add short docstrings. Do not change serialized behavior in this extraction.
- [ ] **Step 3: Introduce `WorkflowDraftAuthoringApi`**
Use an explicit dependency on the lifecycle service:
```python
class WorkflowDraftAuthoringApi:
"""Capability-aware semantic edits over revisioned workflow drafts."""
def __init__(
self,
context: WorkflowOperationContext,
drafts: WorkflowDraftApi,
) -> None:
self.context = context
self.drafts = drafts
```
Move `create_minimal_draft_workspace`, `bind_output_to_state`, and
`add_step_from_capability` into this class. Each operation must call
`self.drafts.patch_draft_workspace` or another public lifecycle method rather
than accessing the store directly for mutation.
- [ ] **Step 4: Preserve the unified facade**
In `WorkflowApi.__init__`:
```python
self.drafts = WorkflowDraftApi(context)
self.draft_authoring = WorkflowDraftAuthoringApi(context, self.drafts)
```
Existing public delegates keep their names and forward semantic calls to
`self.draft_authoring`.
- [ ] **Step 5: Run focused tests**
Run: `uv run pytest tests/wf_api/test_drafts_service.py -q`
Expected: PASS with no public behavior change.
- [ ] **Step 6: Commit**
```bash
git add src/wf_api/draft_authoring.py src/wf_api/draft_payloads.py src/wf_api/drafts.py src/wf_api/service.py tests/wf_api/test_drafts_service.py
git commit -m "refactor: separate draft semantic authoring"
```
### Task 2: Remove The Superseded Partial State Projection Operation
**Files:**
- Modify: `src/wf_api/service.py`
- Modify: `src/wf_api/surface.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: `src/wf_mcp/workflow_surface/models.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Modify: `src/wf_cli/commands/drafts.py`
- Modify: affected draft tests in `tests/`
- [ ] **Step 1: Prove no production caller remains**
Run:
```powershell
rg -n 'add_state_schema_from_output|add-state-from-output' src tests docs skills
```
Expected: only the operation implementation, adapters, tests, and docs refer to
it; no independent production caller depends on it.
- [ ] **Step 2: Remove the operation end to end**
Delete `add_state_schema_from_output`, its request/params DTOs, RPC method,
client method, MCP tool, CLI command, exports, and dedicated tests. Do not add a
compatibility shim. Keep `bind_output_to_state` as the complete semantic
operation.
- [ ] **Step 3: Run surface import and help tests**
Run:
```bash
uv run pytest tests/wf_cli/test_app.py tests/wf_transport_rpc_http/test_client.py tests/wf_mcp/server/test_config.py -q
```
Expected: PASS and no removed command/tool in enumerated surfaces.
- [ ] **Step 4: Commit**
```bash
git add src tests
git commit -m "refactor: remove partial draft state projection"
```
### Task 3: Add Atomic `branch` And `handle` Authoring Operations
**Files:**
- Modify: `src/wf_api/draft_authoring.py`
- Modify: `src/wf_api/service.py`
- Modify: `src/wf_api/surface.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: `src/wf_mcp/workflow_surface/models.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Modify: `src/wf_cli/commands/drafts.py`
- Test: `tests/wf_api/test_drafts_service.py`
- Test: `tests/wf_transport_rpc_http/test_app.py`
- Test: `tests/wf_transport_rpc_http/test_client.py`
- Test: `tests/wf_cli/test_remote_target.py`
- Test: `tests/wf_mcp/server/test_config.py`
- [ ] **Step 1: Write failing API tests**
Cover atomic route updates and preservation:
```python
result = await api.branch_draft(
workspace_id="branching",
revision=1,
step_id="classify",
routes={"ok": "next", "error": "tool_error"},
)
assert result["revision"] == 2
workspace = await api.get_draft_workspace(workspace_id="branching")
assert workspace["draft"]["routes"]["classify"] == {
"ok": "next",
"error": "tool_error",
"retry": "retry_step",
}
```
Add a `handle_draft` test updating two source outcomes in one revision. Assert
empty mappings, duplicate CLI values, unknown source steps, and unknown declared
outcomes leave the workspace byte-for-byte unchanged.
- [ ] **Step 2: Implement semantic methods**
Use protocol-neutral signatures. `branch_draft` takes keyword-only
`workspace_id: str`, `revision: int`, `step_id: str`, and
`routes: dict[str, str]`. `handle_draft` takes keyword-only `workspace_id: str`,
`revision: int`, `branches: Sequence[DraftOutcomeRef]`, and `target: str`. Both
return `dict[str, Any]`.
Define `DraftOutcomeRef` as a small frozen Pydantic model or dataclass with
`step_id` and `outcome`. Validate request-local preconditions first, build one
JSON Patch list, then call `WorkflowDraftApi.patch_draft_workspace` once.
- [ ] **Step 3: Add transport models and methods**
RPC and MCP request models use structured branch records. Register:
```text
workflow.draft_workspaces.branch
workflow.draft_workspaces.handle
wf.workflow.branch_draft
wf.workflow.handle_draft
```
Extend `WorkflowDraftSurface` and the RPC client mixin with the exact API
signatures.
- [ ] **Step 4: Add CLI commands**
Expose:
```powershell
wf draft branch WORKSPACE --revision N --step STEP --route ok=next --route error=fail
wf draft handle WORKSPACE --revision N --to fail --branch lookup:error --branch transform:error
```
Parse route values with the existing strict `KEY=VALUE` utility. Parse branch
values at the final colon, reject duplicates, and send structured records.
- [ ] **Step 5: Run vertical-slice tests**
```bash
uv run pytest 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_cli/test_remote_target.py tests/wf_mcp/server/test_config.py -q
```
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src tests
git commit -m "feat: add semantic draft branch and handle"
```
### Task 4: Make Capability-Step Routing Complete
**Files:**
- Modify: `src/wf_api/draft_authoring.py`
- Modify: `src/wf_api/service.py`
- Modify: `src/wf_api/surface.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_mcp/workflow_surface/models.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Modify: `src/wf_cli/commands/drafts.py`
- Test: affected API/RPC/MCP/CLI draft tests
- [ ] **Step 1: Write failing routing-policy tests**
Cover three cases:
```python
# One declared outcome named "done", no routes supplied.
assert added_routes == {"done": "__end__"}
# No outcome metadata, no routes supplied.
assert added_routes == {"ok": "__end__"}
# Multiple declared outcomes, incomplete explicit routes.
with pytest.raises(ValueError, match="missing routes.*error"):
await api.add_step_from_capability(
workspace_id="multi",
revision=1,
step_id="echo",
capability_name="demo.echo",
routes={"ok": "__end__"},
)
```
Also assert complete multi-outcome routes succeed in one revision.
- [ ] **Step 2: Replace singular route parameters**
Replace `route_outcome` and `route_to` with:
```python
routes: dict[str, str] | None = None
```
Resolve declared capability outcomes before mutation. Infer the sole outcome
regardless of name; use `ok` only when metadata supplies none. For multiple
declared outcomes, require exact coverage and report `declared_outcomes`,
`missing_outcomes`, and `unknown_outcomes` in the application error.
- [ ] **Step 3: Update all public adapters**
RPC and MCP accept a route mapping. CLI replaces singular `--outcome`/`--to`
with repeatable `--route OUTCOME=TARGET`. Update help to explain sole-outcome
inference and multi-outcome completeness.
- [ ] **Step 4: Run focused tests**
```bash
uv run pytest 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_cli/test_app.py tests/wf_cli/test_remote_target.py tests/wf_mcp/server/test_config.py -q
```
Expected: PASS.
- [ ] **Step 5: Commit**
```bash
git add src tests
git commit -m "fix: require complete capability step routes"
```
### Task 5: Compile A Stored Draft Workspace Without Mutation
**Files:**
- Modify: `src/wf_api/drafts.py`
- Modify: `src/wf_api/service.py`
- Modify: `src/wf_api/surface.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: `src/wf_mcp/workflow_surface/models.py`
- Modify: `src/wf_mcp/workflow_surface/tools.py`
- Modify: `src/wf_cli/commands/drafts.py`
- Test: affected API/RPC/MCP/CLI draft tests
- [ ] **Step 1: Write failing no-mutation API tests**
Capture the workspace before and after compilation:
```python
before = await api.get_draft_workspace(workspace_id="compile_me")
result = await api.compile_draft_workspace(workspace_id="compile_me")
after = await api.get_draft_workspace(workspace_id="compile_me")
assert result["compiled_plan"]["name"] == "compile_me"
assert result["required_capabilities"]
assert after == before
```
Add an invalid-workspace test asserting structured diagnostics and no
`compiled_plan`.
- [ ] **Step 2: Implement the read-only projection**
Add:
```python
async def compile_draft_workspace(self, *, workspace_id: str) -> dict[str, Any]:
workspace = self._draft_store().get_workspace(workspace_id)
validation = await self.validate_draft(draft=workspace.draft)
if validation["status"] != "valid":
return validation
return await self.compile_draft(draft=workspace.draft)
```
Do not call `validate_draft_workspace`, because that operation refreshes stored
status and diagnostics.
- [ ] **Step 3: Expose RPC and MCP operations**
Register:
```text
workflow.draft_workspaces.compile
wf.workflow.compile_draft_workspace
```
Return the application envelope containing `compiled_plan` and
`required_capabilities`.
- [ ] **Step 4: Add the CLI projection**
Expose `wf draft compile WORKSPACE`. On success print only
`result["compiled_plan"]`. On invalid status, print the structured diagnostic
envelope to stderr and exit nonzero. Do not add an output-file option.
- [ ] **Step 5: Run focused tests**
```bash
uv run pytest 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_cli/test_app.py tests/wf_cli/test_remote_target.py tests/wf_mcp/workflow_surface/test_drafts.py -q
```
Expected: PASS.
- [ ] **Step 6: Commit**
```bash
git add src tests
git commit -m "feat: compile stored draft workspaces"
```
### Task 6: Documentation, Skills, And End-To-End Regression
**Files:**
- Modify: `docs/workflow_drafts.md`
- Modify: `docs/wf_cli.md`
- Modify: `docs/wf_authoring_control_flow.md`
- Modify: `skills/wf-cli/SKILL.md`
- Modify: `skills/wf-workflow/SKILL.md`
- Modify: `skills/wf-workflow/references/draft-workspaces.md`
- Modify: `skills/wf-workflow/references/workflow-lifecycle.md`
- Modify: `docs/current_roadmap.md`
- Modify: `docs/superpowers/specs/2026-06-27-draft-semantic-authoring-boundary.md`
- Test: `tests/wf_cli/test_remote_target.py`
- Move after completion: `docs/superpowers/plans/2026-06-27-draft-semantic-authoring-and-compile.md` -> `docs/historical/superpowers/plans/2026-06-27-draft-semantic-authoring-and-compile.md`
- [ ] **Step 1: Add a two-step multi-outcome integration regression**
Use the running RPC test fixture or local static server to:
1. create a workspace from a capability;
2. add a second capability declaring `ok` and `error` with both routes;
3. validate without a follow-up `set-route` call;
4. save the artifact and deployment;
5. run it and assert two trace frames completed.
This test must fail against the old singular-route helper.
- [ ] **Step 2: Reorganize public guidance by operation level**
Document semantic operations first, focused edits second, `patch` last. Explain
that `branch` and `handle` mirror `WorkflowBuilder` but mutate `WorkflowDraft`.
Document `compile` as read-only and show that it prints a raw plan without
saving an artifact.
- [ ] **Step 3: Document extensibility without implementing extra step kinds**
State that `WorkflowDraftAuthoringApi` is the home for future semantic helpers
for `interrupt`, `foreach`, condition, join/end, and future core step kinds.
Do not add commands for them in this slice.
- [ ] **Step 4: Update status documents**
Mark the design implemented, record the completed roadmap item, update live
links, and archive this plan.
- [ ] **Step 5: Run final verification**
```bash
uv run pytest 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_cli/test_app.py tests/wf_cli/test_remote_target.py tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_config.py -q
uv run ruff check
uv run ruff format --check
uv run basedpyright --level error
git diff --check
```
Expected: all tests pass, Ruff is clean, basedpyright reports zero errors, and
there are no whitespace errors.
- [ ] **Step 6: Archive and commit**
```bash
git mv docs/superpowers/plans/2026-06-27-draft-semantic-authoring-and-compile.md docs/historical/superpowers/plans/2026-06-27-draft-semantic-authoring-and-compile.md
git add docs skills tests
git commit -m "docs: record semantic draft authoring surface"
```
@@ -0,0 +1,362 @@
# Draft Semantic Authoring Boundary
Date: 2026-06-27
Status: Approved for implementation planning.
Related:
- [Workflow drafts](../../workflow_drafts.md)
- [`wf_authoring` control flow](../../wf_authoring_control_flow.md)
- [Workflow API architecture](../../wf_api_architecture.md)
- [Current roadmap](../../current_roadmap.md)
## Purpose
Define a stable boundary between the persisted draft document, revisioned draft
workspaces, and capability-aware authoring operations intended for agents.
The draft system must remain one authoring model rather than becoming a second
workflow language beside `WorkflowBuilder`. Semantic operations therefore use
the same control-flow vocabulary as `WorkflowBuilder`, but lower each intent to
one atomic patch against the persisted `WorkflowDraft` representation.
## Current Problem
The current `WorkflowDraftApi` owns several distinct responsibilities:
- draft parsing, validation, and compilation;
- revisioned workspace lifecycle and JSON Patch application;
- low-level focused document edits;
- capability lookup and schema projection;
- compound authoring operations such as adding a capability step.
The responsibilities are individually valid, but keeping them in one service
obscures the boundary and allows semantic behavior to diverge from
`WorkflowBuilder`. The existing `add_step_from_capability` demonstrates this:
it writes one outgoing route even when the capability declares multiple
outcomes, producing a draft that immediately fails validation.
## Core Decisions
### One Persisted Authoring Representation
`WorkflowDraft` remains the only persisted authoring representation. It is a
patch-friendly intermediate form with keyed `steps`, `routes`, schemas, and
explicit bindings. `RawWorkflowPlan` remains the normalized execution model.
```text
semantic authoring intent
-> atomic WorkflowDraft patch
-> revision check
-> compile and validate
-> persisted WorkflowDraft workspace
-> RawWorkflowPlan projection
```
Semantic operations do not persist builder objects, create an additional graph
model, or modify raw plans in place.
### Separate Semantic Authoring Service
Introduce `WorkflowDraftAuthoringApi` as a sibling of `WorkflowDraftApi`.
`WorkflowDraftApi` owns:
- workspace create, get, list, delete, and revision handling;
- draft validation and compilation;
- raw JSON Patch;
- low-level focused document edits;
- read-only projection of a stored workspace to `RawWorkflowPlan`.
`WorkflowDraftAuthoringApi` owns:
- capability-aware draft bootstrap;
- semantic construction of draft step kinds;
- adding a capability-backed `use` step;
- projecting and binding capability outputs into state;
- `branch` and `handle` semantic routing operations.
`WorkflowDraftAuthoringApi` depends on `WorkflowDraftApi` for workspace access
and patch application. It must not write the workspace store directly. Every
semantic operation produces one patch and consumes one revision.
`WorkflowApi` continues to expose one protocol-neutral facade. RPC, MCP, and CLI
clients do not need to know about the internal service split.
The service boundary is intentionally not capability-only. The current draft
model also represents `end`, `condition`, `interrupt`, `foreach`, `join`,
`when`, `choose`, and `match` steps, and core may gain more step kinds. This
slice adds semantic operations only where required, but new step-kind helpers
belong in `WorkflowDraftAuthoringApi` rather than a parallel authoring system.
### Match `WorkflowBuilder` Vocabulary
The semantic draft surface uses the established authoring terms:
- `branch`: connect several outcomes from one existing step to targets;
- `handle`: connect several source-step/outcome pairs to one shared target.
These operations add or replace edges only. They do not create condition steps,
wait for concurrent branches, or implement join semantics. `match`, `when`, and
`choose` remain outside this first slice.
## Public Operation Levels
The public draft surface is documented in descending order of preference.
### Semantic Authoring Operations
- `create-from-capability`
- `add-step-from-capability`
- `bind-output-to-state`
- `branch`
- `handle`
These operations understand capability definitions, schemas, outcomes, or
graph intent. They are the preferred agent authoring surface.
### Low-Level Focused Edits
- `set-name`
- `set-route`
- `set-input`
- `set-output`
These remain available for precise repairs. `set-output` does not project the
destination state schema; callers should prefer `bind-output-to-state` when
writing a capability output into state.
### Escape Hatch
`patch` remains the RFC 6902 escape hatch for structural edits that semantic or
focused operations do not cover.
### Lifecycle And Projection
- `list`
- `inspect`
- `validate`
- `compile`
- `save`
- `delete`
These operations manage or inspect the workspace rather than expressing graph
authoring intent.
## Operation Contracts
### Add Step From Capability
`add-step-from-capability` atomically adds:
- one capability-backed `use` step;
- explicit input bindings;
- output-to-state bindings and required state schema projection;
- an optional incoming route;
- the complete outgoing route map.
The outgoing CLI option is repeatable:
```powershell
wf draft add-step-from-capability WORKSPACE `
--revision 4 `
--step second_echo `
--capability everything.default.echo `
--route ok=next `
--route error=tool_error
```
When the caller supplies no routes and the capability declares exactly one
outcome, the operation infers that outcome, regardless of its name, and routes
it to `__end__`. If capability metadata declares no outcomes, the inferred
outcome is `ok`. When a capability has multiple known outcomes, the caller must
provide a target for every outcome. Missing or unknown outcomes reject the
operation before mutation and report the declared outcomes. Callers can always
override the target by supplying an explicit route.
The current singular `route_outcome` and `route_to` shape has no known external
caller or persisted-data dependency and is replaced rather than retained as
ghost compatibility behavior.
### Branch
`branch` applies several outcome routes from one existing step in one revision:
```powershell
wf draft branch WORKSPACE --revision 5 --step classify `
--route send=send_email `
--route skip=__end__ `
--route error=tool_error
```
Supplied outcomes add or replace their route. Routes for outcomes omitted from
the request remain unchanged. The operation rejects an empty route map, unknown
source step, unknown declared outcome, or malformed target before mutation.
Normal workflow validation remains responsible for missing required outcomes,
unknown target steps, and broader graph consistency.
### Handle
`handle` redirects several source-step/outcome pairs to one shared target:
```powershell
wf draft handle WORKSPACE --revision 6 --to tool_error `
--branch lookup:error `
--branch transform:error
```
The transport request uses structured pairs rather than encoded strings. The
CLI parses each `STEP:OUTCOME` value at the final colon and rejects malformed
values before making the request. Existing routes unrelated to the supplied
pairs remain unchanged.
`handle` is not a join. It creates ordinary directed edges to one target.
### Bind Output To State
`bind-output-to-state` remains the capability-aware schema propagation
operation. It projects the selected output property and required `$defs` into
the root state schema, then merges the output binding in the same revision.
The partial `add-state-from-output` operation is removed from API, RPC, MCP,
CLI, docs, and skills. It was superseded before acquiring a real caller or
persisted-data contract.
### Compile A Stored Workspace
Add a read-only workspace projection:
```powershell
wf draft compile WORKSPACE
```
The server operation:
1. reads the stored workspace;
2. validates it in memory against current capability definitions;
3. compiles it through the existing draft adapter;
4. returns `compiled_plan` and required capability metadata.
The CLI prints only the bare `compiled_plan` JSON so it can be inspected or
piped directly into another command. The operation does not save an artifact,
refresh stored diagnostics, increment the revision, or otherwise mutate the
workspace.
An invalid workspace returns structured diagnostics and a nonzero CLI exit. It
must not emit a partial raw plan.
## Validation And Error Behavior
All semantic mutations use the current workspace revision. A stale revision,
malformed request, unknown capability, or semantic precondition failure leaves
the workspace unchanged.
Once a semantic patch is constructed, it passes through the existing workspace
patch path. That path performs the revision check, draft parsing, compilation,
structural validation, persistence, and refreshed diagnostics.
Known request-local mistakes should fail before mutation with specific data:
- multi-outcome step missing routes: include missing and declared outcomes;
- branch with unknown outcome: include the step's declared outcomes;
- handle with unknown source step: identify the missing step;
- duplicate route or branch values in one CLI invocation: reject as ambiguous;
- invalid compile: return the same structured diagnostic vocabulary as draft
validation.
Draft workspaces may remain invalid during iterative low-level editing. Semantic
operations should avoid creating a known-invalid result when all required
information is already available in the request and capability catalog.
## Transport Shape
The protocol-neutral API uses mappings and structured records:
- branch routes: `dict[str, str]` mapping outcome to target;
- handle branches: a list of `{step_id, outcome}` records plus one target;
- add-step routes: `dict[str, str]` mapping every declared outcome to target.
RPC request models and MCP request models mirror those shapes. CLI parsing is a
front-end concern and must not leak encoded `STEP:OUTCOME` strings into the
application API.
## Canonical Path Strings
Authoring surfaces use one canonical TOML-key path grammar. Examples include:
```text
state.report.title
input."customer.name"
local.items
```
The underlying `GraphSourcePath`, `StatePath`, and `LocalPath` models remain
structured typed values. Strings are the public and serialized representation;
the models parse those strings once at their boundary.
Move TOML-key parsing from the `wf_authoring` convenience layer into `wf_core`
so CLI, RPC, MCP, drafts, raw plans, and Python authoring use the same parser and
formatter. The shared grammar must support quoted TOML keys for literal dots,
spaces, and other non-bare segments. Parse errors identify the complete input
and recommend quoting the invalid segment.
Pydantic JSON schemas advertise path strings rather than the structural
`{root, parts}` object. Serializers emit canonical strings. Validators continue
to accept the structural object only as a read-compatibility path for existing
persisted drafts, artifacts, and runs; new public examples and writes use
strings. This is compatibility for real stored data, not a second documented
syntax.
## Compatibility And Migration
No workflow semantics or draft field layout changes. The serialized path
representation changes from structural objects to canonical strings. Existing
workspaces, artifacts, deployments, and runs remain readable: their path
objects are accepted on input and become canonical strings when a containing
record is rewritten.
Low-level operations remain available. The migration changes only compound
operation signatures and removes the unused partial schema helper. Repository
callers, tests, docs, and skills are updated in the same slice. No compatibility
shim is added without a real external caller.
## Testing Strategy
### Domain And Service Tests
- branch merges supplied routes and preserves unrelated routes;
- handle updates several source routes atomically;
- malformed requests and stale revisions do not mutate the workspace;
- multi-outcome add-step requires complete routes;
- route inference chooses the sole declared outcome even when it is not `ok`;
- absent outcome metadata falls back to `ok`;
- an inferred route targets `__end__` unless explicitly overridden;
- output binding still projects referenced schema definitions;
- all path models parse and serialize the canonical TOML-key string grammar;
- structural path objects remain readable but are not emitted;
- stored-workspace compile equals `compile_workflow_draft` output;
- compile does not change revision, timestamps, status, or diagnostics.
### Surface Tests
- RPC and client methods preserve structured route data;
- MCP tools expose branch, handle, and workspace compile;
- CLI repeatable options parse into the protocol-neutral request shape;
- CLI compile prints only the raw plan and exits nonzero for invalid drafts;
- help text distinguishes semantic operations, low-level edits, and JSON Patch.
### Integration Regression
Build a two-step workflow where the second capability declares `ok` and
`error`. Add it with complete routes, save the artifact and deployment, run it,
and verify both steps execute without requiring a follow-up `set-route` repair.
## Non-Goals
- adding `match`, `when`, or `choose` draft commands in this slice;
- changing workflow semantics or the `WorkflowDraft` field layout;
- replacing JSON Patch;
- treating `handle` as synchronization or join behavior;
- automatic semantic compatibility analysis between connected schemas;
- saving artifacts as a side effect of compile.