Files
lda-wf/docs/historical/superpowers/plans/2026-07-23-atomic-step-output-bindings.md
T

28 KiB

Atomic Step Output Bindings Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add one revision-checked operation that atomically replaces a capability step's ordered canonical output bindings while projecting missing workflow state schemas.

Architecture: Reuse the core OutputBinding model from API through transport. Put capability-aware source validation and state-schema projection in WorkflowDraftAuthoringApi, use the existing wf_api.schema_projection helpers, and retain the map operation only as a compatibility adapter. The CLI lowers ordered flags or a canonical JSON file into the same binding list.

Tech Stack: Python 3.14, Pydantic 2, JSON Schema Draft 2020-12, Typer, FastAPI JSON-RPC, FastMCP, pytest, Ruff, basedpyright.

Global Constraints

  • Do not add another output-binding model or mapping language.
  • Preserve canonical binding order and permit one local source to feed several distinct state targets.
  • Reject duplicate and ancestor/descendant state targets before mutation.
  • Validate local sources against the selected capability output schema.
  • Project missing state target schemas atomically; never delete state schema when replacing or clearing bindings.
  • Accept an existing target schema only when it is exactly equal to the selected source fragment.
  • Envelope validation precedes revision checking; stale revision then precedes workspace, capability, and schema semantics.
  • Changed replacements advance exactly one revision; exact replacements are revision-checked no-ops.
  • Keep the legacy map API/RPC and --merge behavior operational for real callers and label it compatibility-only and lossy.
  • Do not change workflow-output bindings, runtime binding models, TypeScript RPC, or revision history.
  • Add comments around whole-payload projection, monotonic state-schema projection, and CLI compatibility dispatch.

Task 1: Atomic Capability-Aware Output Replacement

Files:

  • Modify: src/wf_api/draft_authoring.py
  • Modify: src/wf_api/surface.py
  • Modify: src/wf_api/service.py
  • Test: tests/wf_api/test_drafts_service.py
  • Test: tests/core/test_atomic_state_patches.py

Interfaces:

  • Consumes: OutputBinding, schema_fragment_at_path, schema_path_exists, project_schema_path_to_schema_path, WorkflowDraftAuthoringApi._workspace_if_revision_matches, and WorkflowDraftApi.patch_draft_workspace.

  • Produces:

    async def set_step_output_bindings(
        *,
        workspace_id: str,
        revision: int,
        step_id: str,
        bindings: Sequence[OutputBinding],
    ) -> dict[str, Any]: ...
    
  • Preserves: set_step_output_map(..., merge=...) unchanged as a compatibility operation.

  • Step 1: Write failing canonical replacement and fan-out tests

    Add tests alongside the canonical input-binding tests. Use an existing test capability whose output schema contains a nested object, or register one with an output contract equivalent to:

    {
        "type": "object",
        "properties": {
            "report": {
                "type": "object",
                "properties": {
                    "title": {"type": "string"},
                    "markdown": {"type": "string"},
                },
                "required": ["title", "markdown"],
            }
        },
        "required": ["report"],
    }
    

    Pin exact stored order and source fan-out:

    result = await api.set_step_output_bindings(
        workspace_id="draft-output-bindings",
        revision=1,
        step_id="analyze",
        bindings=[
            OutputBinding(
                source=LocalPath.parse("report.title"),
                target=StatePath.parse("state.report.title"),
            ),
            OutputBinding(
                source=LocalPath.parse("report.title"),
                target=StatePath.parse("state.audit.title"),
            ),
        ],
    )
    
    assert result["revision"] == 2
    inspected = await api.get_draft_workspace(
        workspace_id="draft-output-bindings",
        include_draft=True,
    )
    assert inspected["draft"]["steps"]["analyze"]["output"] == [
        {"source": "report.title", "target": "state.report.title"},
        {"source": "report.title", "target": "state.audit.title"},
    ]
    
  • Step 2: Write failing schema projection tests

    Cover nested source-to-target projection, whole-payload . projection, exact-equivalent existing target acceptance, and incompatible existing target rejection. Assert projected fields rather than whole schema equality:

    state_schema = inspected["draft"]["state_schema"]
    assert state_schema["properties"]["report"]["properties"]["title"] == {
        "type": "string"
    }
    assert state_schema["properties"]["audit"]["properties"]["title"] == {
        "type": "string"
    }
    

    For whole payload:

    bindings=[
        OutputBinding(
            source=LocalPath.root(),
            target=StatePath.parse("state.raw_result"),
        )
    ]
    

    Verify the complete capability output object appears below state_schema.properties.raw_result.

  • Step 3: Write failing semantic-error and revision tests

    Add cases for:

    • missing local source;
    • duplicate state target;
    • state.report overlapping state.report.title;
    • incompatible existing state target schema;
    • missing or non-capability step;
    • stale revision combined with each semantic error class;
    • unchanged draft and revision after every failure;
    • exact replacement returning a no-op summary at the current revision;
    • bindings=[] clearing outputs without deleting projected state fields.

    Error assertions should include stable binding indexes:

    with pytest.raises(
        ValueError,
        match=r"bindings\[0\]\.target 'state\.report' overlaps "
        r"bindings\[1\]\.target 'state\.report\.title'",
    ):
        await api.set_step_output_bindings(...)
    
  • Step 4: Run focused API tests and confirm RED

    Run:

    uv run pytest tests/wf_api/test_drafts_service.py -q -k "step_output"
    

    Expected: failures because WorkflowApi.set_step_output_bindings does not exist.

  • Step 5: Add output overlap and patch helpers

    In src/wf_api/draft_authoring.py, add focused helpers beside the input equivalents:

    def _overlapping_output_targets_error(
        bindings: Sequence[OutputBinding],
    ) -> ValueError:
        """Describe the first overlapping state-target pair with stable indexes."""
        for left_index, left in enumerate(bindings):
            for right_index in range(left_index + 1, len(bindings)):
                right = bindings[right_index]
                if paths_overlap(str(left.target), str(right.target)):
                    return ValueError(
                        f"bindings[{left_index}].target {str(left.target)!r} "
                        f"overlaps bindings[{right_index}].target "
                        f"{str(right.target)!r}"
                    )
        raise AssertionError("overlap error requested without overlapping targets")
    
    
    def _step_output_bindings_patch(
        *,
        workspace: WorkflowDraftWorkspace,
        step_id: str,
        bindings: list[dict[str, Any]],
        state_schema: dict[str, Any],
    ) -> list[dict[str, Any]]:
        """Build one atomic patch for state schema and canonical step outputs."""
        patch: list[dict[str, Any]] = []
        if workspace.draft.get("state_schema", {}) != state_schema:
            patch.append(
                {"op": "replace", "path": "/state_schema", "value": state_schema}
            )
        patch.append(
            {
                "op": "replace",
                "path": f"/steps/{escape_json_pointer(step_id)}/output",
                "value": bindings,
            }
        )
        return patch
    

    paths_overlap operates on rootless path syntax, so passing both serialized state.* values gives it a shared synthetic root and preserves equality and ancestry checks. Keep this comment at the helper call because the type seam is otherwise non-obvious.

  • Step 6: Implement canonical output replacement

    Add WorkflowDraftAuthoringApi.set_step_output_bindings:

    async def set_step_output_bindings(
        self,
        *,
        workspace_id: str,
        revision: int,
        step_id: str,
        bindings: Sequence[OutputBinding],
    ) -> dict[str, Any]:
        """Replace one capability step's canonical output bindings atomically."""
        checked = self._workspace_if_revision_matches(
            workspace_id=workspace_id,
            revision=revision,
        )
        if isinstance(checked, dict):
            return checked
        workspace = checked
        step = draft_step(workspace.draft, step_id)
        capability_name = step.get("use")
        if not isinstance(capability_name, str) or not capability_name:
            raise ValueError(
                f"draft step {step_id!r} does not declare a capability use"
            )
        spec = self.context.specs.get_qualified_spec(capability_name)
        capability_schema = (
            spec.output_schema_contract or spec.output_model.model_json_schema()
        )
    
        targets = [str(binding.target) for binding in bindings]
        if has_overlapping_paths(targets):
            raise _overlapping_output_targets_error(bindings)
    
        projected_state = _draft_schema(workspace.draft, "state_schema")
        for index, binding in enumerate(bindings):
            source_parts = binding.source.parts
            try:
                schema_fragment_at_path(
                    capability_schema,
                    source_parts,
                    label="capability output schema",
                )
            except ValueError as exc:
                raise ValueError(
                    f"bindings[{index}].source {str(binding.source)!r} "
                    f"is not declared by capability {capability_name!r}: {exc}"
                ) from exc
    
            target_parts = binding.target.parts
            try:
                projected_state = project_schema_path_to_schema_path(
                    target_schema=projected_state,
                    source_schema=capability_schema,
                    source_parts=source_parts,
                    target_parts=target_parts,
                    allow_existing_equivalent=True,
                )
            except ValueError as exc:
                raise ValueError(
                    f"bindings[{index}].target {str(binding.target)!r} "
                    f"cannot receive source {str(binding.source)!r}: {exc}"
                ) from exc
    
        payload = [binding.model_dump(mode="json") for binding in bindings]
        if (
            step.get("output", []) == payload
            and workspace.draft.get("state_schema", {}) == projected_state
        ):
            return summarize_draft_workspace(workspace)
    
        return await self.drafts.patch_draft_workspace(
            workspace_id=workspace_id,
            revision=revision,
            patch=_step_output_bindings_patch(
                workspace=workspace,
                step_id=step_id,
                bindings=payload,
                state_schema=projected_state,
            ),
        )
    

    The unconditional projection call naturally inserts missing targets and accepts only exact existing targets. Wrap its errors with the binding index, target, and source so incompatible-schema diagnostics satisfy the public error contract.

  • Step 7: Expose the operation through the API protocol and service

    Add the same Sequence[OutputBinding] signature to WorkflowApiSurface and delegate from WorkflowApi to self.draft_authoring exactly as the canonical input operation does.

  • Step 8: Add compile-and-run fan-out coverage

    In tests/core/test_atomic_state_patches.py, add a focused apply_output_bindings regression where one local source is bound to two state targets. Assert both state values after application. The authoring API test separately proves the same canonical list compiles. Do not only inspect serialized draft JSON.

  • Step 9: Run API and runtime tests

    Run:

    uv run pytest tests/wf_api/test_drafts_service.py tests/core/test_atomic_state_patches.py -q
    

    Expected: PASS.

  • Step 10: Commit Task 1

    git add src/wf_api/draft_authoring.py src/wf_api/surface.py src/wf_api/service.py tests/wf_api/test_drafts_service.py tests/core/test_atomic_state_patches.py
    git commit -m "feat: replace draft step output bindings"
    

Task 2: JSON-RPC Model, Method, And Remote Client

Files:

  • Modify: src/wf_transport_rpc_http/models.py
  • Modify: src/wf_transport_rpc_http/__init__.py
  • Modify: src/wf_transport_rpc_http/methods/drafts.py
  • Modify: src/wf_transport_rpc_http/client/drafts.py
  • Test: tests/wf_transport_rpc_http/test_app.py
  • Test: tests/wf_transport_rpc_http/test_client.py

Interfaces:

  • Consumes: Task 1 WorkflowApi.set_step_output_bindings and core OutputBinding.

  • Produces:

    class SetStepOutputBindingsParams(RpcParamsModel):
        workspace_id: str
        revision: int
        step_id: str
        bindings: list[OutputBinding]
    

    and remote client method set_step_output_bindings(...).

  • Step 1: Write failing RPC application tests

    Add a request containing duplicate sources and ordered distinct targets:

    response = await _rpc(
        app,
        "workflow.draft_workspaces.set_step_output_bindings",
        {
            "workspace_id": "draft-rpc-output-bindings",
            "revision": 1,
            "step_id": "analyze",
            "bindings": [
                {"source": "report.title", "target": "state.report.title"},
                {"source": "report.title", "target": "state.audit.title"},
            ],
        },
    )
    

    Inspect the workspace and assert exact list order. Add malformed requests for missing source, bare state target, unexpected fields, and invalid root marker. These must return JSON-RPC invalid-params errors without invoking the semantic method.

  • Step 2: Write failing remote client tests

    Call the typed client with two OutputBinding values sharing a source. Assert the recorded wire method and JSON payload exactly:

    assert call["method"] == (
        "workflow.draft_workspaces.set_step_output_bindings"
    )
    assert call["params"]["bindings"] == [
        {"source": "report.title", "target": "state.report.title"},
        {"source": "report.title", "target": "state.audit.title"},
    ]
    
  • Step 3: Run RPC tests and confirm RED

    uv run pytest tests/wf_transport_rpc_http/test_app.py tests/wf_transport_rpc_http/test_client.py -q -k "step_output"
    

    Expected: unknown method/model/client failures.

  • Step 4: Add the typed parameter model and exports

    Import OutputBinding in models.py, define SetStepOutputBindingsParams, and add it to wf_transport_rpc_http.__init__ imports and __all__.

  • Step 5: Register the JSON-RPC method

    In methods/drafts.py, register:

    @method(
        name="workflow.draft_workspaces.set_step_output_bindings",
        params_model=SetStepOutputBindingsParams,
    )
    async def workflow_draft_workspaces_set_step_output_bindings(
        params: SetStepOutputBindingsParams = RpcParams(),
    ) -> dict[str, Any]:
        return await server.api.set_step_output_bindings(
            workspace_id=params.workspace_id,
            revision=params.revision,
            step_id=params.step_id,
            bindings=params.bindings,
        )
    

    Follow the existing decorator signature exactly if it differs from this abbreviated example.

  • Step 6: Add the remote client method

    Mirror set_step_input_bindings and serialize each binding with model_dump(mode="json"). Do not lower through output_map.

  • Step 7: Run RPC tests

    uv run pytest tests/wf_transport_rpc_http/test_app.py tests/wf_transport_rpc_http/test_client.py -q
    

    Expected: PASS.

  • Step 8: Commit Task 2

    git add src/wf_transport_rpc_http tests/wf_transport_rpc_http
    git commit -m "feat: expose output bindings over rpc"
    

Task 3: MCP Canonical Output Tool

Files:

  • Modify: src/wf_mcp/workflow_surface/models.py
  • Modify: src/wf_mcp/workflow_surface/tools.py
  • Modify: src/wf_mcp/proxy/runtime.py
  • Test: tests/wf_mcp/workflow_surface/test_drafts.py
  • Test: tests/wf_mcp/server/test_tools.py
  • Test: tests/wf_mcp/server/test_config.py

Interfaces:

  • Consumes: Task 1 WorkflowApiSurface.set_step_output_bindings and core OutputBinding.

  • Produces:

    class SetStepOutputBindingsRequest(BaseModel):
        workspace_id: WorkspaceId
        revision: int
        step_id: NonEmptyString
        bindings: DraftOutputBindings
    

    and MCP tool wf.workflow.set_step_output_bindings.

  • Step 1: Write failing request-model tests

    Validate ordered fan-out and reject malformed canonical records:

    request = SetStepOutputBindingsRequest.model_validate(
        {
            "workspace_id": "draft-output",
            "revision": 4,
            "step_id": "analyze",
            "bindings": [
                {"source": "report.title", "target": "state.report.title"},
                {"source": "report.title", "target": "state.audit.title"},
            ],
        }
    )
    
    assert [str(binding.source) for binding in request.bindings] == [
        "report.title",
        "report.title",
    ]
    
  • Step 2: Write failing handler and discovery tests

    Use the workflow-surface handler fake to assert the tool delegates once with typed bindings in order. Add the tool name to server discovery/config tests and the always-visible proxy list assertion.

  • Step 3: Run focused MCP tests and confirm RED

    uv run pytest tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_tools.py tests/wf_mcp/server/test_config.py -q -k "output_bindings or tools or config"
    
  • Step 4: Add the MCP request model

    Define SetStepOutputBindingsRequest beside the map request and describe it as complete ordered replacement. Reuse DraftOutputBindings; do not define a second union or path-map type.

  • Step 5: Register the MCP tool

    Add:

    @mcp.tool(
        name="wf.workflow.set_step_output_bindings",
        description=(
            "Replace one capability step's complete ordered output bindings. "
            "Repeated sources are valid fan-out; state targets must not overlap."
        ),
    )
    async def set_step_output_bindings(
        request: SetStepOutputBindingsRequest,
    ) -> dict[str, Any]:
        return dict(
            await handlers.set_step_output_bindings(
                workspace_id=request.workspace_id,
                revision=request.revision,
                step_id=request.step_id,
                bindings=request.bindings,
            )
        )
    

    Follow local registration and result-conversion conventions exactly.

  • Step 6: Pin the tool in proxy discovery

    Add wf.workflow.set_step_output_bindings beside input bindings in _SEARCH_ALWAYS_VISIBLE_TOOL_NAMES.

  • Step 7: Run MCP tests

    uv run pytest tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_tools.py tests/wf_mcp/server/test_config.py -q
    

    Expected: PASS.

  • Step 8: Commit Task 3

    git add src/wf_mcp tests/wf_mcp
    git commit -m "feat: expose canonical output bindings to mcp"
    

Task 4: Typer Replacement Modes And Remote CLI

Files:

  • Modify: src/wf_cli/commands/draft_options.py
  • Modify: src/wf_cli/commands/drafts.py
  • Test: tests/wf_cli/test_app.py
  • Test: tests/wf_cli/test_remote_target.py

Interfaces:

  • Consumes: core OutputBinding, Task 1 local API operation, and Task 2 remote client method.

  • Produces:

    def parse_step_output_binding_flags(
        values: list[str] | None,
    ) -> list[OutputBinding]: ...
    
    def parse_step_output_bindings_file(path: Path) -> list[OutputBinding]: ...
    
  • Keeps: wf draft set-output; no new command name.

  • Step 1: Write failing parser tests

    Add tests proving repeated sources remain separate and file order is exact:

    bindings = parse_step_output_binding_flags(
        [
            "report.title=state.report.title",
            "report.title=state.audit.title",
        ]
    )
    assert [binding.model_dump(mode="json") for binding in bindings] == [
        {"source": "report.title", "target": "state.report.title"},
        {"source": "report.title", "target": "state.audit.title"},
    ]
    

    File tests must reject a JSON object, missing fields, unexpected fields, invalid local source syntax, and non-state targets as concise typer.BadParameter errors.

  • Step 2: Write failing local command tests

    Cover:

    • repeated --map canonical replacement;
    • --bindings-file replacement;
    • explicit --clear;
    • no mode selected;
    • --bindings-file or --clear combined with --map;
    • --merge combined with file/clear;
    • --merge --map dispatching to set_step_output_map only;
    • duplicate-source fan-out dispatching to set_step_output_bindings only.
  • Step 3: Write failing remote-target tests

    Run the same repeated-source and file modes with --target. Assert the RPC method is exactly workflow.draft_workspaces.set_step_output_bindings, called once, with list order preserved. Retain one compatibility --merge assertion for set_step_output_map.

  • Step 4: Run focused CLI tests and confirm RED

    uv run pytest tests/wf_cli/test_app.py tests/wf_cli/test_remote_target.py -q -k "set_output"
    
  • Step 5: Add output binding parsers

    In draft_options.py, import OutputBinding, add a TypeAdapter(list[OutputBinding]), parse ordered flags directly rather than through _parse_assignment_flags, and translate Pydantic/path failures with validation_error_as_bad_parameter:

    def parse_step_output_binding_flags(
        values: list[str] | None,
    ) -> list[OutputBinding]:
        """Parse ordered local-to-state outputs without collapsing fan-out."""
        bindings: list[OutputBinding] = []
        for item in values or []:
            source, separator, target = item.partition("=")
            if separator != "=" or not source or not target:
                raise typer.BadParameter(
                    "--map must use LOCAL_SOURCE=STATE_TARGET"
                )
            try:
                bindings.append(
                    OutputBinding(
                        source=LocalPath.parse(source),
                        target=StatePath.parse(target),
                    )
                )
            except (PathResolutionError, ValidationError) as exc:
                raise typer.BadParameter(str(exc)) from exc
        return bindings
    

    parse_step_output_bindings_file validates parse_json_file(...) through the adapter exactly as the input file parser does.

  • Step 6: Rework wf draft set-output dispatch

    Add --bindings-file: Path | None and --clear: bool. Use this mode matrix:

    has_maps = bool(mapping)
    has_file = bindings_file is not None
    selected_modes = sum((has_maps, has_file, clear))
    if selected_modes == 0:
        raise typer.BadParameter("provide --map, --bindings-file, or --clear")
    if selected_modes > 1:
        raise typer.BadParameter(
            "--bindings-file and --clear cannot be combined with --map"
        )
    if merge and (has_file or clear):
        raise typer.BadParameter(
            "--merge is supported only for compatibility map-only edits"
        )
    

    Dispatch --merge --map through _parse_output_map_flags and set_step_output_map. Dispatch every replacement mode through canonical bindings and set_step_output_bindings. The no-flags case must no longer silently clear outputs; clearing requires --clear.

  • Step 7: Update command help tests

    Pin direction and semantics:

    --map LOCAL_SOURCE=STATE_TARGET
    --bindings-file ordered canonical JSON array
    --clear replace with no bindings
    --merge compatibility-only and potentially lossy
    
  • Step 8: Run CLI tests

    uv run pytest tests/wf_cli/test_app.py tests/wf_cli/test_remote_target.py -q
    

    Expected: PASS.

  • Step 9: Commit Task 4

    git add src/wf_cli tests/wf_cli
    git commit -m "feat: replace draft output bindings from cli"
    

Task 5: Documentation, Issue State, Review, And Final Verification

Files:

  • Modify: ISSUES.md
  • Modify: skills/wf-cli/SKILL.md
  • Modify: skills/wf-workflow/references/draft-workspaces.md
  • Modify: skills/wf-workflow/references/workflow-lifecycle.md
  • Modify: docs/workflow_capabilities.md
  • Modify: docs/wf_mcp_operator_manual.md
  • Modify: docs/current_roadmap.md
  • Move after completion: docs/superpowers/plans/2026-07-23-atomic-step-output-bindings.md to docs/historical/superpowers/plans/2026-07-23-atomic-step-output-bindings.md

Interfaces:

  • Consumes: all completed implementation tasks and their verified command names.

  • Produces: current user/agent guidance, accurate issue state, archived checked plan, and a final verification report.

  • Step 1: Update issue language without falsely closing map loss

    Keep the combined issue unchecked and revise it to state:

    - [ ] Compatibility step input/output maps can still collapse valid canonical
      fan-out bindings. Canonical input and output replacement preserve ordered
      fan-out, but later compatibility-map merges remain inherently lossy.
    

    Leave workflow-output literals, nested workflow-output projection, focused step updates, and TypeScript parity unchecked.

  • Step 2: Update CLI and agent documentation

    Replace descriptions that say set-output replaces a map with canonical-list language. Include examples for repeated-source fan-out, file round-trip, explicit clear, and compatibility merge:

    wf draft set-output WS --revision 4 --step analyze \
      --map report.title=state.report.title \
      --map report.title=state.audit.title
    
    wf draft inspect WS --include-draft |
      jq '.draft.steps.analyze.output' > output-bindings.json
    
    wf draft set-output WS --revision 5 --step analyze \
      --bindings-file output-bindings.json
    
    wf draft set-output WS --revision 6 --step analyze --clear
    

    Explicitly warn that --merge --map is compatibility-only and may collapse existing fan-out.

  • Step 3: Update RPC/MCP operation inventories

    Add workflow.draft_workspaces.set_step_output_bindings and wf.workflow.set_step_output_bindings to user-facing inventories and examples near their input counterparts.

  • Step 4: Run focused matrix verification

    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 \
      tests/wf_mcp/server/test_tools.py \
      tests/wf_mcp/server/test_config.py \
      tests/wf_cli/test_app.py \
      tests/wf_cli/test_remote_target.py \
      -q
    

    Expected: PASS with only already-known dependency deprecation warnings.

  • Step 5: Run static verification

    uv run ruff check
    uv run ruff format --check
    uv run basedpyright --level error
    git diff --check
    

    Expected: all clean.

  • Step 6: Run independent review

    Use the repository code-review skill against the pre-slice commit. Fix every Critical or Important finding. Record Minor deferrals with concrete reasons in the final report.

  • Step 7: Update roadmap and archive this plan

    Add a completed roadmap entry linking to:

    historical/superpowers/plans/2026-07-23-atomic-step-output-bindings.md
    

    Check every completed plan box, move the plan under docs/historical/, and verify no live link still points to its old location.

  • Step 8: Commit documentation and completion state

    git add ISSUES.md skills docs
    git commit -m "docs: complete atomic step output bindings"
    
  • Step 9: Final repository check

    git status --short
    git log -7 --oneline
    

    Expected: clean worktree and one coherent commit per task plus any explicitly identified review-fix commit.

Plan Self-Review

  • The plan implements every approved spec surface: Python API, RPC, MCP, CLI, compatibility behavior, docs, and issue state.
  • The canonical model stays OutputBinding; no map or new union appears on the replacement path.
  • State schema projection uses the existing shared helper and is monotonic.
  • Fan-out is tested at serialization and runtime boundaries.
  • Revision precedence, no-op behavior, clear behavior, and no-mutation failures are explicit.
  • Workflow-output authoring, focused step updates, and TypeScript parity remain separate slices.