docs: plan semantic draft authoring
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user