21 KiB
Draft Semantic Revision Precedence 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: Ensure every content-aware draft authoring helper returns the canonical revision_conflict result before inspecting current draft content or capability metadata for a stale request.
Architecture: Reuse WorkflowDraftAuthoringApi._workspace_if_revision_matches as the shared semantic preflight for all seven affected helpers. Keep WorkflowDraftApi.patch_draft_workspace as the second, mutation-time optimistic-lock guard so public error precedence is deterministic without weakening race safety.
Tech Stack: Python 3.14, pytest with pytest-asyncio, Ruff, basedpyright
Global Constraints
- Follow
draft semantic revision precedence design. - Request-envelope validation that does not read workspace or catalog state remains before revision preflight.
- After envelope validation, stale revision conflict wins over all workspace-content and capability-catalog errors.
- Missing-workspace behavior remains unchanged.
- Current-revision semantic errors, successful edit shapes, and no-op revision behavior remain unchanged.
- Every mutation still delegates to
patch_draft_workspace; do not replace or bypass its mutation-time revision check. - Do not change Python API, JSON-RPC, remote-client, CLI, or TypeScript signatures.
- Do not add a decorator, exception-translation layer, new storage API, or compatibility branch.
- Do not stage or modify the ACL-protected untracked
pytest-of-lda/directory.
File Map
- Modify
src/wf_api/draft_authoring.py: generalize the revision helper and move all seven semantic helpers behind it. - Modify
tests/wf_api/test_drafts_service.py: add stale-plus-semantic-defect regressions, envelope-precedence coverage, and a direct mutation-guard characterization. - Modify
ISSUES.md: check only the resolvedDraft revision semanticsitem after all gates pass. - Modify
docs/current_roadmap.md: add the completed slice with a link to the archived plan. - Move this plan to
docs/historical/superpowers/plans/2026-07-22-draft-semantic-revision-precedence.mdafter completion.
Task 1: Gate Capability-Aware Semantic Edits
Files:
- Modify:
src/wf_api/draft_authoring.py:104-124 - Modify:
src/wf_api/draft_authoring.py:323-694 - Test:
tests/wf_api/test_drafts_service.py
Interfaces:
-
Consumes:
WorkflowDraftAuthoringApi._workspace_if_revision_matches(*, workspace_id: str, revision: int) -> WorkflowDraftWorkspace | dict[str, Any]. -
Produces: unchanged
bind_draft(...) -> dict[str, Any]andadd_step_from_capability(...) -> dict[str, Any]behavior, with revision preflight beforedraft_step, schema projection, or capability lookup. -
Preserves:
WorkflowDraftApi.patch_draft_workspace(...)as the final mutation-time guard. -
Step 1: Add a direct characterization test for the final mutation guard
Add this test near
test_patch_draft_workspace_updates_revisionintests/wf_api/test_drafts_service.py:@pytest.mark.asyncio async def test_patch_draft_workspace_stale_revision_does_not_mutate( tmp_path: Path, ) -> None: artifact_store = FileWorkflowArtifactStore( tmp_path / "drafts_patch_workspace_stale" ) api, _service, _authoring = _draft_api(artifact_store, register_echo=True) await api.create_draft_workspace( workspace_id="echo_ws", draft=_echo_draft(), ) before = await api.get_draft_workspace( workspace_id="echo_ws", include_draft=True, ) result = await api.patch_draft_workspace( workspace_id="echo_ws", revision=2, patch=[{"op": "replace", "path": "/name", "value": "must_not_apply"}], ) after = await api.get_draft_workspace( workspace_id="echo_ws", include_draft=True, ) assert result["status"] == "conflict" assert result["diagnostics"][0]["code"] == "revision_conflict" assert after == before -
Step 2: Run the mutation-guard characterization
Run:
uv run pytest tests/wf_api/test_drafts_service.py::test_patch_draft_workspace_stale_revision_does_not_mutate -q -n 0 --basetemp C:\tmp\pytest-draft-revision-guardExpected: PASS before production changes. This pins the existing second revision check that the semantic preflight must not replace.
-
Step 3: Add failing stale-versus-capability-semantic tests
Add this test near
test_add_step_stale_revision_wins_over_content_preflight:@pytest.mark.asyncio @pytest.mark.parametrize("operation", ["bind", "capability_add"]) async def test_capability_aware_edits_stale_revision_wins_over_semantic_errors( tmp_path: Path, operation: str, ) -> None: artifact_store = FileWorkflowArtifactStore( tmp_path / f"draft_capability_stale_{operation}" ) api, _service, authoring = _draft_api(artifact_store, register_echo=True) await api.create_draft_workspace( workspace_id="draft_ws", draft=_echo_draft(), ) before = await api.get_draft_workspace( workspace_id="draft_ws", include_draft=True, ) if operation == "bind": result = await authoring.bind_draft( workspace_id="draft_ws", revision=2, step_id="missing", source_path="input.text", target_path="local.text", ) else: result = await authoring.add_step_from_capability( workspace_id="draft_ws", revision=2, step_id="new_step", capability_name="missing.connection.unknown_tool", ) after = await api.get_draft_workspace( workspace_id="draft_ws", include_draft=True, ) assert result["status"] == "conflict" assert result["revision"] == before["revision"] assert result["diagnostics"][0]["code"] == "revision_conflict" assert after == before -
Step 4: Run the new semantic-precedence test and confirm it is red
Run:
uv run pytest tests/wf_api/test_drafts_service.py::test_capability_aware_edits_stale_revision_wins_over_semantic_errors -q -n 0 --basetemp C:\tmp\pytest-draft-revision-capability-redExpected: FAIL because
bind_draftraises for the missing step andadd_step_from_capabilityraises during unknown capability lookup before returning a conflict. -
Step 5: Generalize the shared revision helper documentation
In
WorkflowDraftAuthoringApi._workspace_if_revision_matches, replace the no-op-specific docstring with:"""Load a workspace and enforce optimistic locking before semantic preflight."""Keep its return shape and diagnostic construction unchanged.
-
Step 6: Gate
bind_draftbefore draft and catalog inspectionReplace the direct workspace load at the start of
bind_draftwith:checked = self._workspace_if_revision_matches( workspace_id=workspace_id, revision=revision, ) if isinstance(checked, dict): return checked workspace = checkedLeave all subsequent
draft_step, capability lookup, path parsing, schema projection, and patch construction unchanged. -
Step 7: Gate
add_step_from_capabilitybefore draft and catalog inspectionReplace the direct workspace load at the start of
add_step_from_capabilitywith:checked = self._workspace_if_revision_matches( workspace_id=workspace_id, revision=revision, ) if isinstance(checked, dict): return checked workspace = checkedThis block must precede
workspace.draft.get("steps")andself.context.specs.get_qualified_spec(capability_name). -
Step 8: Run focused capability-aware tests
Run:
uv run pytest tests/wf_api/test_drafts_service.py -q -n 0 --basetemp C:\tmp\pytest-draft-revision-capability -k 'bind_draft or add_step_from_capability or patch_draft_workspace_stale_revision'Expected: PASS. Existing current-revision bind and capability-add success/error tests must remain unchanged.
-
Step 9: Run focused quality checks
Run:
uv run ruff check src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py uv run ruff format --check src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py uv run basedpyright src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py --level errorExpected: all commands exit 0.
-
Step 10: Commit Task 1
git add src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py git commit -m "fix: gate capability draft edits on revision"
Task 2: Normalize Route And Removal Revision Precedence
Files:
- Modify:
src/wf_api/draft_authoring.py:694-925 - Test:
tests/wf_api/test_drafts_service.py
Interfaces:
-
Consumes: the generalized
_workspace_if_revision_matchesfrom Task 1. -
Produces: unchanged
branch_draft,handle_draft,remove_draft_route,remove_draft_step, andremove_draft_bindingsignatures with one early revision gate each. -
Preserves: the request-envelope
remove_draft_bindingselection check before workspace lookup and all current-revision no-op summaries without revision increments. -
Step 1: Add failing stale-versus-content-semantic tests
Add these tests in the branch/handle and remove-helper section of
tests/wf_api/test_drafts_service.py:@pytest.mark.asyncio @pytest.mark.parametrize( "operation", ["branch", "handle", "remove_route", "remove_step", "remove_binding"], ) async def test_route_and_remove_edits_stale_revision_wins_over_semantic_errors( tmp_path: Path, operation: str, ) -> None: artifact_store = FileWorkflowArtifactStore( tmp_path / f"draft_route_remove_stale_{operation}" ) api, _service, authoring = _draft_api(artifact_store, register_echo=True) draft = _echo_draft() if operation in {"branch", "handle", "remove_route"}: draft["routes"] = "not-an-object" elif operation == "remove_step": draft["steps"] = "not-an-object" await api.create_draft_workspace(workspace_id="draft_ws", draft=draft) before = await api.get_draft_workspace( workspace_id="draft_ws", include_draft=True, ) if operation == "branch": result = await authoring.branch_draft( workspace_id="draft_ws", revision=2, step_id="echo", routes={"error": "__end__"}, ) elif operation == "handle": result = await authoring.handle_draft( workspace_id="draft_ws", revision=2, branches=[RouteSource(step_id="echo", outcome="error")], target="__end__", ) elif operation == "remove_route": result = await authoring.remove_draft_route( workspace_id="draft_ws", revision=2, step_id="echo", outcome="ok", ) elif operation == "remove_step": result = await authoring.remove_draft_step( workspace_id="draft_ws", revision=2, step_id="echo", ) else: result = await authoring.remove_draft_binding( workspace_id="draft_ws", revision=2, step_id="missing", inputs=("text",), ) after = await api.get_draft_workspace( workspace_id="draft_ws", include_draft=True, ) assert result["status"] == "conflict" assert result["revision"] == before["revision"] assert result["diagnostics"][0]["code"] == "revision_conflict" assert after == before @pytest.mark.asyncio async def test_remove_draft_binding_envelope_error_precedes_revision_check( tmp_path: Path, ) -> None: artifact_store = FileWorkflowArtifactStore( tmp_path / "draft_remove_binding_envelope_precedence" ) api, _service, authoring = _draft_api(artifact_store, register_echo=True) await api.create_draft_workspace(workspace_id="draft_ws", draft=_echo_draft()) before = await api.get_draft_workspace( workspace_id="draft_ws", include_draft=True, ) with pytest.raises( ValueError, match="pass at least one input or output binding to remove", ): await authoring.remove_draft_binding( workspace_id="draft_ws", revision=2, step_id="echo", ) after = await api.get_draft_workspace( workspace_id="draft_ws", include_draft=True, ) assert after == before -
Step 2: Run the new route/removal tests and confirm the semantic cases are red
Run:
uv run pytest tests/wf_api/test_drafts_service.py -q -n 0 --basetemp C:\tmp\pytest-draft-revision-route-red -k 'route_and_remove_edits_stale_revision or remove_draft_binding_envelope_error'Expected: the five parameterized semantic-precedence cases FAIL under current code; the request-envelope test PASSes and guards the intended exception ordering.
-
Step 3: Introduce one reusable early-gate pattern in all five helpers
In each of
branch_draft,handle_draft,remove_draft_route, andremove_draft_step, replace the first direct workspace load or pre-content no-op branch with:checked = self._workspace_if_revision_matches( workspace_id=workspace_id, revision=revision, ) if isinstance(checked, dict): return checked workspace = checkedIn
remove_draft_binding, keep this envelope validation first:if not inputs and not outputs: raise ValueError("pass at least one input or output binding to remove")Then insert the same early-gate block immediately after it. No other helper in this task has request-envelope validation that must remain ahead of revision preflight.
-
Step 4: Simplify no-op exits to use the checked workspace
Replace each late
_workspace_if_revision_matchescall in no-op branches with:return summarize_draft_workspace(workspace)Apply this to:
branch_draftwhenmerged == existing;handle_draftwhenbranchesis empty;handle_draftwhen no patch entries were produced;remove_draft_routewhen the outcome is absent;remove_draft_stepwhen the step is absent;remove_draft_bindingwhen selected bindings are absent.
Do not add a revision increment for any no-op.
-
Step 5: Run the route/removal precedence and existing no-op tests
Run:
uv run pytest tests/wf_api/test_drafts_service.py -q -n 0 --basetemp C:\tmp\pytest-draft-revision-route -k 'branch_draft or handle_draft or remove_draft or route_and_remove_edits_stale_revision'Expected: PASS, including existing no-op tests and malformed current-revision binding tests.
-
Step 6: Run the complete draft service suite
Run:
uv run pytest tests/wf_api/test_drafts_service.py -q -n 0 --basetemp C:\tmp\pytest-draft-revision-serviceExpected: PASS with no current-revision response-shape regressions.
-
Step 7: Run focused quality checks
Run:
uv run ruff check src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py uv run ruff format --check src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py uv run basedpyright src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py --level errorExpected: all commands exit 0.
-
Step 8: Commit Task 2
git add src/wf_api/draft_authoring.py tests/wf_api/test_drafts_service.py git commit -m "fix: normalize draft semantic revision precedence"
Task 3: Cross-Layer Verification, Issue Closure, And Plan Archive
Files:
- Modify:
ISSUES.md - Modify:
docs/current_roadmap.md - Move:
docs/superpowers/plans/2026-07-22-draft-semantic-revision-precedence.md - To:
docs/historical/superpowers/plans/2026-07-22-draft-semantic-revision-precedence.md
Interfaces:
-
Consumes: the unchanged public API/RPC/client/CLI contracts and corrected delegated conflict behavior from Tasks 1-2.
-
Produces: verified behavior, one closed issue, one live-roadmap completion entry, and one archived implementation plan.
-
Step 1: Run the relevant API/RPC/client/CLI regression suites
Run:
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 -q -n 0 --basetemp C:\tmp\pytest-draft-revision-finalExpected: PASS. These suites confirm that unchanged transports continue delegating the conflict envelope rather than rewriting it.
-
Step 2: Run final Python quality gates
Run:
uv run ruff check src/wf_api src/wf_transport_rpc_http src/wf_cli tests/wf_api tests/wf_transport_rpc_http tests/wf_cli uv run ruff format --check src/wf_api src/wf_transport_rpc_http src/wf_cli tests/wf_api tests/wf_transport_rpc_http tests/wf_cli uv run basedpyright --level error git diff --checkExpected: every command exits 0. Do not broaden the implementation merely to silence unrelated pre-existing findings; report any such finding before changing scope.
-
Step 3: Run independent code review against the design contract
Use the
code-reviewskill to review the Task 1-2 diff againstdocs/superpowers/specs/2026-07-22-draft-semantic-revision-precedence-design.md. Require the reviewer to check:- all seven listed helpers gate semantic work exactly once;
remove_draft_bindingenvelope validation still precedes the gate;- no-op branches use the already checked workspace;
- every mutation still reaches
patch_draft_workspacewith the caller revision; - response shapes and public signatures are unchanged;
- tests prove both canonical conflict and no mutation.
Fix any Critical or Important finding, rerun the focused suite and quality gates, and commit the fix separately with a specific
fix:message. -
Step 4: Close only the verified issue
Replace the
Draft revision semanticsitem inISSUES.mdwith:## Draft revision semantics - [x] Semantic draft edits consistently check the expected revision before reading current draft content or capability metadata. After request-envelope validation, stale callers receive the canonical `revision_conflict` result; mutation-time revision checking remains the final race-safe guard.Leave the data-shaping, step-metadata, and TypeScript JSON-RPC coverage items unchecked.
-
Step 5: Record completion in the live roadmap
Add this entry beside the other completed draft-authoring work in
docs/current_roadmap.md:- Completed: semantic draft edits now gate workspace and capability preflight on the expected revision, so stale callers consistently receive `revision_conflict` while the patch path retains its mutation-time race guard. Implementation: [`draft semantic revision precedence`](historical/superpowers/plans/2026-07-22-draft-semantic-revision-precedence.md). -
Step 6: Archive the completed plan and update this checklist
Mark every completed checkbox in this plan, then run:
git mv docs/superpowers/plans/2026-07-22-draft-semantic-revision-precedence.md docs/historical/superpowers/plans/2026-07-22-draft-semantic-revision-precedence.md rg -n '2026-07-22-draft-semantic-revision-precedence' docs ISSUES.mdExpected: the spec remains live under
docs/superpowers/specs/; the roadmap points to the historical plan; no live document points to the old active-plan path. -
Step 7: Commit the verified documentation state
git add ISSUES.md docs/current_roadmap.md docs/superpowers/plans/2026-07-22-draft-semantic-revision-precedence.md docs/historical/superpowers/plans/2026-07-22-draft-semantic-revision-precedence.md git commit -m "docs: complete draft revision precedence" -
Step 8: Confirm final repository state
Run:
git status --short git log -3 --onelineExpected: no tracked changes remain. The ACL-protected
pytest-of-lda/directory may remain untracked and must not be staged.