docs: complete atomic step input bindings

This commit is contained in:
lda
2026-07-23 00:26:05 +07:00 Verified
parent 88e51901a7
commit 1f6e3a5925
7 changed files with 155 additions and 67 deletions
+8 -6
View File
@@ -14,16 +14,18 @@
- [x] Capability-step authoring persists nested input targets but silently skips
workflow input/state schema projection when a target has more than one path
segment.
- [ ] No atomic API/RPC/CLI helper assembles one structured node input from
multiple graph paths. The current focused path requires several
revision-checked edits and an intermediate state object, or a raw map/patch.
- [ ] Focused draft authoring cannot add or update literal node-input bindings
- [x] An atomic API/RPC/CLI helper assembles one structured node input from
multiple graph paths. Canonical replacement accepts several bindings in one
revision-checked edit without an intermediate state object or raw JSON Patch.
- [x] Focused draft authoring can add or replace literal node-input bindings
comparable to `WorkflowBuilder.use(input=[{"target": ..., "value": ...}])`
without raw JSON Patch.
through canonical API, RPC, MCP, and CLI surfaces.
- [ ] Focused step input/output maps collapse valid canonical fan-out bindings.
A source-to-target dictionary cannot represent one graph source feeding two
local inputs, or one local output feeding two state targets; a later merge can
therefore rewrite a valid binding list into a lossy map.
therefore rewrite a valid binding list into a lossy map. Canonical step-input
replacement now preserves fan-out, but compatibility map readers/writers and
focused step-output maps remain lossy.
- [ ] Focused workflow-output authoring cannot add or update literal output
bindings even though `WorkflowDraft.output` accepts canonical value bindings.
- [ ] Workflow output schema projection skips nested sources such as
+5
View File
@@ -805,6 +805,11 @@ stable.
direct add/remove repair guidance.
- Completed: repeated idempotent `wf draft bind input/state -> local` behavior
is covered by regression tests.
- Completed: capability-aware canonical step-input replacement now supports
structured assembly, literals, explicit null, fan-out, whole-payload
projection, local/remote CLI modes, JSON-RPC, and MCP. Compatibility map
merge remains available but intentionally lossy. Implementation plan:
[`atomic step input bindings`](historical/superpowers/plans/2026-07-22-atomic-step-input-bindings.md).
Agent evaluation cohort status and policy:
@@ -52,7 +52,7 @@
- Extends: `project_schema_path_to_schema_path` accepts an empty `source_parts` tuple to project the complete source schema for whole-payload target `.`.
- [ ] **Step 1: Write failing schema-fragment tests**
- [x] **Step 1: Write failing schema-fragment tests**
Add tests covering an inline nested field, a selected `$ref` leaf that needs
root `$defs`, the root fragment for `parts=()`, and a remote selected ref:
@@ -87,7 +87,7 @@
assert schema_fragment_at_path(schema, ()) == schema
```
- [ ] **Step 2: Write failing literal-validation tests**
- [x] **Step 2: Write failing literal-validation tests**
Pin valid/invalid strings, objects, arrays, and `null`, including a selected
schema behind a local reference:
@@ -124,7 +124,7 @@
)
```
- [ ] **Step 3: Write a failing whole-schema projection test**
- [x] **Step 3: Write a failing whole-schema projection test**
```python
def test_project_schema_path_accepts_whole_source_schema() -> None:
@@ -142,7 +142,7 @@
assert projected["properties"]["payload"]["required"] == ["title"]
```
- [ ] **Step 4: Run the focused tests and confirm RED**
- [x] **Step 4: Run the focused tests and confirm RED**
Run:
@@ -153,7 +153,7 @@
Expected: failures because the two public helpers do not exist and whole
source projection rejects an empty source path.
- [ ] **Step 5: Implement fragment selection and literal validation**
- [x] **Step 5: Implement fragment selection and literal validation**
Import JSON Schema's instance-validation error separately from schema errors,
then add:
@@ -247,7 +247,7 @@
target_defs[name] = deepcopy(definition)
```
- [ ] **Step 6: Run focused tests and quality checks**
- [x] **Step 6: Run focused tests and quality checks**
Run:
@@ -260,7 +260,7 @@
Expected: all pass with no diagnostics.
- [ ] **Step 7: Commit Task 1**
- [x] **Step 7: Commit Task 1**
```bash
git add src/wf_api/schema_projection.py tests/wf_api/test_schema_projection.py
@@ -281,7 +281,7 @@
- Consumes: `schema_fragment_at_path`, `validate_json_value_at_schema_path`, `project_schema_path_to_schema_path`, `has_overlapping_paths`, canonical `InputBinding` models.
- Produces: `WorkflowApiSurface.set_step_input_bindings(...)` with the exact signature from the design spec.
- [ ] **Step 1: Write failing structured-assembly and fan-out API tests**
- [x] **Step 1: Write failing structured-assembly and fan-out API tests**
Reuse the nested report capability fixture from the preceding nested-binding
slice. Add one test that replaces a step input list with two nested path
@@ -319,7 +319,7 @@
Add a separate fan-out assertion using the same `state.report.title` source
for `request.title` and `audit.title`.
- [ ] **Step 2: Write failing semantic-error and no-mutation tests**
- [x] **Step 2: Write failing semantic-error and no-mutation tests**
Add parameterized tests for missing target, duplicate target,
ancestor/descendant overlap, invalid literal, unsupported remote target ref,
@@ -329,7 +329,7 @@
Add stale-revision cases paired with missing target and invalid literal; both
must return `revision_conflict` before those semantic errors.
- [ ] **Step 3: Write failing projection, whole-payload, context, and no-op tests**
- [x] **Step 3: Write failing projection, whole-payload, context, and no-op tests**
Cover:
@@ -344,7 +344,7 @@
binding that changes no workflow schema, explicit valid `null`, and an exact
second replacement that leaves the revision unchanged.
- [ ] **Step 4: Run API tests and confirm RED**
- [x] **Step 4: Run API tests and confirm RED**
Run:
@@ -354,7 +354,7 @@
Expected: failures because the surface and implementation method do not exist.
- [ ] **Step 5: Implement the semantic operation**
- [x] **Step 5: Implement the semantic operation**
Add imports for `InputPathBinding`, `InputValueBinding`,
`has_overlapping_paths`, and the Task 1 schema helpers. Add this method to
@@ -516,7 +516,7 @@
return patch
```
- [ ] **Step 6: Add the protocol-neutral delegation**
- [x] **Step 6: Add the protocol-neutral delegation**
Add the exact method signature to `WorkflowApiSurface` and delegate from
`WorkflowApi`:
@@ -538,7 +538,7 @@
)
```
- [ ] **Step 7: Run focused API and runtime tests**
- [x] **Step 7: Run focused API and runtime tests**
Run:
@@ -553,7 +553,7 @@
asserting the handler receives `request.title`, `request.body`, and
`request.format` in one object.
- [ ] **Step 8: Commit Task 2**
- [x] **Step 8: Commit Task 2**
```bash
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
@@ -575,7 +575,7 @@
- Consumes: `WorkflowApiSurface.set_step_input_bindings` from Task 2.
- Produces: JSON-RPC method `workflow.draft_workspaces.set_step_input_bindings` and matching `RpcDraftClientMixin` method.
- [ ] **Step 1: Write failing RPC model and application tests**
- [x] **Step 1: Write failing RPC model and application tests**
Add an application test that sends this request and then inspects the stored
order:
@@ -601,7 +601,7 @@
Add malformed union tests for a binding with both `path` and `value`, and one
with neither.
- [ ] **Step 2: Write a failing remote-client serialization test**
- [x] **Step 2: Write a failing remote-client serialization test**
```python
await client.set_step_input_bindings(
@@ -628,7 +628,7 @@
)
```
- [ ] **Step 3: Run transport tests and confirm RED**
- [x] **Step 3: Run transport tests and confirm RED**
Run:
@@ -639,7 +639,7 @@
Expected: failures for the missing params model, method registration, and
client method.
- [ ] **Step 4: Implement the RPC params, method, and client**
- [x] **Step 4: Implement the RPC params, method, and client**
In `models.py`, reuse the core union:
@@ -674,7 +674,7 @@
Add the client method using `model_dump(mode="json")` for every binding.
- [ ] **Step 5: Run transport verification**
- [x] **Step 5: Run transport verification**
Run:
@@ -686,7 +686,7 @@
Expected: all pass.
- [ ] **Step 6: Commit Task 3**
- [x] **Step 6: Commit Task 3**
```bash
git add src/wf_transport_rpc_http tests/wf_transport_rpc_http/test_app.py tests/wf_transport_rpc_http/test_client.py
@@ -709,7 +709,7 @@
- Consumes: Task 2 API method and existing `DraftInputBindings` alias.
- Produces: MCP tool `wf.workflow.set_step_input_bindings`.
- [ ] **Step 1: Write failing MCP request and handler tests**
- [x] **Step 1: Write failing MCP request and handler tests**
Add a request-model test proving path/value union parsing and explicit null.
Add a workflow-surface test that calls the new tool handler with ordered
@@ -717,7 +717,7 @@
for the tool name and that its request schema exposes `bindings` but no
`merge`.
- [ ] **Step 2: Run MCP tests and confirm RED**
- [x] **Step 2: Run MCP tests and confirm RED**
Run:
@@ -727,7 +727,7 @@
Expected: failures because the request model and tool are absent.
- [ ] **Step 3: Add the request model and tool**
- [x] **Step 3: Add the request model and tool**
Reuse the existing alias:
@@ -769,7 +769,7 @@
Add the tool name to the proxy runtime allowlist next to
`wf.workflow.set_step_input_map`. Keep the compatibility tool registered.
- [ ] **Step 4: Run MCP verification**
- [x] **Step 4: Run MCP verification**
Run:
@@ -781,7 +781,7 @@
Expected: all pass and the generated MCP schema contains the canonical union.
- [ ] **Step 5: Commit Task 4**
- [x] **Step 5: Commit Task 4**
```bash
git add src/wf_mcp/workflow_surface src/wf_mcp/proxy/runtime.py tests/wf_mcp/workflow_surface/test_drafts.py tests/wf_mcp/server/test_config.py tests/wf_mcp/server/test_tools.py
@@ -803,7 +803,7 @@
- Consumes: `WorkflowApiSurface.set_step_input_bindings` and the legacy `set_step_input_map` adapter.
- Produces: canonical `wf draft set-input` replacement modes described by the spec.
- [ ] **Step 1: Write failing parser tests**
- [x] **Step 1: Write failing parser tests**
Add tests for a list-preserving path parser that accepts duplicate sources,
a literal parser that preserves explicit null and JSON strings containing
@@ -827,7 +827,7 @@
assert values[1].value is None
```
- [ ] **Step 2: Write failing command-mode tests**
- [x] **Step 2: Write failing command-mode tests**
Cover:
@@ -843,7 +843,7 @@
Assert compact `typer.BadParameter` text and that handlers are not called on
invalid combinations.
- [ ] **Step 3: Run CLI tests and confirm RED**
- [x] **Step 3: Run CLI tests and confirm RED**
Run:
@@ -853,7 +853,7 @@
Expected: failures for missing options, parsers, and handler delegation.
- [ ] **Step 4: Implement canonical CLI parsers**
- [x] **Step 4: Implement canonical CLI parsers**
Keep `_parse_step_input_map_flags` unchanged for compatibility. Add list-based
helpers using canonical models:
@@ -918,7 +918,7 @@
seven current call sites. This keeps all Pydantic-to-Typer formatting in one
helper rather than duplicating it.
- [ ] **Step 5: Implement command mode selection**
- [x] **Step 5: Implement command mode selection**
Add `--value`, `--bindings-file`, and `--clear` options to `set-input`. Keep
`--merge` but describe it as compatibility-only. Use explicit mode checks:
@@ -945,7 +945,7 @@
file order, `[]`, or path flags followed by literal flags, and call
`set_step_input_bindings`.
- [ ] **Step 6: Add a real remote CLI round trip**
- [x] **Step 6: Add a real remote CLI round trip**
Extend `tests/wf_cli/test_remote_target.py` to start the local JSON-RPC app,
create/inspect a draft, export the current input list to a test JSON file,
@@ -954,7 +954,7 @@
`workflow.draft_workspaces.set_step_input_bindings` and the revision advances
once.
- [ ] **Step 7: Run CLI verification**
- [x] **Step 7: Run CLI verification**
Run:
@@ -967,7 +967,7 @@
Expected: all pass. Run `uv run wf draft set-input --help` and verify the
output distinguishes replacement modes from compatibility `--merge`.
- [ ] **Step 8: Commit Task 5**
- [x] **Step 8: Commit Task 5**
```bash
git add src/wf_cli/commands/draft_options.py src/wf_cli/commands/draft_add.py src/wf_cli/commands/drafts.py tests/wf_cli/test_app.py tests/wf_cli/test_remote_target.py
@@ -990,7 +990,7 @@
- Consumes: all completed behavior from Tasks 1-5.
- Produces: current user/agent guidance and an archived completed plan.
- [ ] **Step 1: Update user and agent documentation**
- [x] **Step 1: Update user and agent documentation**
Document these exact workflows:
@@ -1012,7 +1012,7 @@
State plainly that replacement is default, `--bindings-file` is canonical,
repeated source paths are allowed, and `--merge` is map-only compatibility.
- [ ] **Step 2: Update issue and roadmap state narrowly**
- [x] **Step 2: Update issue and roadmap state narrowly**
In `ISSUES.md`, check:
@@ -1025,7 +1025,7 @@
Add one completed roadmap entry linking to the historical plan path.
- [ ] **Step 3: Run the complete focused verification matrix**
- [x] **Step 3: Run the complete focused verification matrix**
Run:
@@ -1051,7 +1051,7 @@
Expected: all tests pass; Ruff and basedpyright report no errors. Existing
third-party deprecation warnings may remain but must be reported.
- [ ] **Step 4: Run independent review and fix valid findings**
- [x] **Step 4: Run independent review and fix valid findings**
Use the repository code-review workflow against the design spec and this
plan. Require reviewers to check:
@@ -1068,7 +1068,7 @@
Apply valid fixes and rerun the affected focused tests plus Ruff and
basedpyright.
- [ ] **Step 5: Complete and archive the plan**
- [x] **Step 5: Complete and archive the plan**
Check every completed task box, then move:
@@ -1079,7 +1079,7 @@
Confirm all live links use the historical path.
- [ ] **Step 6: Commit Task 6**
- [x] **Step 6: Commit Task 6**
```bash
git add docs/wf_cli.md skills/wf-cli/SKILL.md \
@@ -1089,7 +1089,7 @@
git commit -m "docs: complete atomic step input bindings"
```
- [ ] **Step 7: Confirm final repository state**
- [x] **Step 7: Confirm final repository state**
Run:
+35 -8
View File
@@ -284,9 +284,9 @@ expose it; the focused helper projects the workflow input schema:
wf draft bind report_ws --revision 2 --step call --from input.path --to local.path
```
Use `wf draft set-input --merge` instead when adding several explicit mappings
for fields already declared in the workflow input or state schema.
to an existing step input map.
Use `wf draft set-input` with repeated `--map` flags when replacing several
bindings for fields already declared in the workflow input or state schema.
Add `--merge` only for a compatibility map-only edit to the existing list.
List and inspect drafts:
@@ -332,7 +332,33 @@ wf draft compile concat_ws
`input.title -> local.report.title`. Targets are rootless node-local paths:
write `--map input.title=report.title`, not
`--map input.title=local.report.title`. Existing single-field targets such as
`input.text=text` remain valid.
`input.text=text` remain valid. Repeating a graph source is allowed, so one
source can populate multiple local targets without being collapsed into a map.
Canonical input replacement supports path bindings, literal JSON values,
ordered binding files, and clearing the complete list:
```bash
wf draft inspect WS --include-draft |
jq '.draft.steps.publish.input' > bindings.json
wf draft set-input WS --revision 4 --step publish \
--map state.report.title=request.title \
--map state.report.markdown=request.body \
--value request.format='"markdown"'
wf draft set-input WS --revision 5 --step publish \
--bindings-file bindings.json
wf draft set-input WS --revision 6 --step publish --clear
```
Replacement is the default. `--bindings-file` is the canonical lossless form
when binding order, literals, or repeated source paths matter. `--merge` is a
compatibility-only option for map-only `--map` edits; it cannot be combined
with `--value`, `--bindings-file`, or `--clear`. Existing literal bindings are
retained, but merge cannot add literals or preserve canonical ordering and
repeated-source fan-out.
`set-output` maps node-local output fields to workflow state paths:
`text=state.text` means `local.text -> state.text`.
@@ -345,10 +371,11 @@ For single-field `input.*` and `state.*` sources, the command projects missing
top-level `output_schema` fields from the source schema. More complex or
undeclared paths still rely on `wf draft validate` diagnostics.
By default, `set-input`, `set-output`, and `set-workflow-output` replace the
whole map for that step or output scope. Use repeated `--map` flags in one
command when you know the complete map. Use `--merge` when adding or updating
one entry across a later revision while preserving existing bindings.
By default, `set-input` replaces the complete ordered input-binding list, while
`set-output` and `set-workflow-output` replace their complete maps. Use repeated
flags in one command when you know the complete replacement. Use `--merge` only
for compatibility map edits; they cannot add literals or preserve canonical
ordering and repeated-source fan-out.
### Bind A Step Path
+30 -4
View File
@@ -43,6 +43,9 @@ wf draft set-start <workspace_id> --revision <n> --step <step_id>
wf draft set-contract <workspace_id> --revision <n> --state-schema-file state.schema.json --outcome ok --outcome error
wf draft set-route <workspace_id> --revision <n> --step <step_id> --outcome <outcome> --to <target>
wf draft set-input <workspace_id> --revision <n> --step <step_id> --map input.title=report.title
wf draft set-input <workspace_id> --revision <n> --step <step_id> --value request.format='"markdown"'
wf draft set-input <workspace_id> --revision <n> --step <step_id> --bindings-file bindings.json
wf draft set-input <workspace_id> --revision <n> --step <step_id> --clear
wf draft set-input <workspace_id> --revision <n> --step <step_id> --merge --map input.other=other
wf draft set-output <workspace_id> --revision <n> --step <step_id> --map text=state.text
wf draft set-output <workspace_id> --revision <n> --step <step_id> --merge --map other=state.other
@@ -78,7 +81,7 @@ reported in wrapper-hint notes; bind them explicitly only when the workflow
should expose them. Use `wf draft bind --from input.x --to local.x` for an
existing step when schema projection may be needed; it is safe if the schema
field already exists. Use `wf draft set-input --merge --map input.x=x` for a
pure input-map edit when the workflow schema is already declared.
compatibility map-only edit when the workflow schema is already declared.
`wf draft bind` names both endpoints explicitly, so local paths keep the
`local.` root. `set-input` and `draft add capability --input` already imply the
@@ -120,9 +123,32 @@ Use `wf schema <name> --verbose` only when the complete JSON Schema is required;
the default compact outline is preferred for agent context. `--full` is accepted
as an alias for `--verbose`.
For `draft set-input` and `draft set-output`, repeated `--map` flags in one
command define the complete replacement map. If you split map edits across
multiple commands, pass `--merge` or the later command replaces the earlier map.
For `draft set-input`, repeated `--map` and `--value` flags define the complete
ordered replacement list. Repeated graph sources are valid and preserve
fan-out. Use `--bindings-file` for the canonical lossless JSON form, or
`--clear` to replace the list with `[]`. `--merge` is compatibility-only and
accepts map-only `--map` edits; compatibility map readers/writers cannot
preserve repeated-source fan-out.
Export, edit, restore, or clear canonical bindings as follows:
```bash
wf draft inspect WS --include-draft |
jq '.draft.steps.publish.input' > bindings.json
wf draft set-input WS --revision 4 --step publish \
--map state.report.title=request.title \
--map state.report.markdown=request.body \
--value request.format='"markdown"'
wf draft set-input WS --revision 5 --step publish \
--bindings-file bindings.json
wf draft set-input WS --revision 6 --step publish --clear
```
For `draft set-output`, repeated `--map` flags still define the complete
replacement map. Pass `--merge` only when deliberately using that map adapter.
Prefer `draft bind` when a capability step binding also needs schema
projection. Use `input/state -> local` for step inputs and `local ->
@@ -100,6 +100,9 @@ CLI equivalents:
wf draft set-name <workspace_id> --revision <n> --name <name>
wf draft set-route <workspace_id> --revision <n> --step <step_id> --outcome ok --to <target>
wf draft set-input <workspace_id> --revision <n> --step <step_id> --map input.title=report.title
wf draft set-input <workspace_id> --revision <n> --step <step_id> --value request.format='"markdown"'
wf draft set-input <workspace_id> --revision <n> --step <step_id> --bindings-file bindings.json
wf draft set-input <workspace_id> --revision <n> --step <step_id> --clear
wf draft set-input <workspace_id> --revision <n> --step <step_id> --merge --map input.other=other
wf draft set-output <workspace_id> --revision <n> --step <step_id> --map text=state.text
wf draft set-output <workspace_id> --revision <n> --step <step_id> --merge --map other=state.other
@@ -124,21 +127,44 @@ fields are projected automatically from the source schema.
`set-input` direction: `input.title=report.title` means graph source
`input.title` maps to node-local target `local.report.title`. Targets are
rootless node-local paths; never prefix the target with `local.`. Existing
single-field targets such as `input.text=text` remain valid.
single-field targets such as `input.text=text` remain valid. Repeated graph
sources are allowed and preserve fan-out to distinct local targets.
Canonical replacement can mix ordered path and literal bindings:
```bash
wf draft inspect WS --include-draft |
jq '.draft.steps.publish.input' > bindings.json
wf draft set-input WS --revision 4 --step publish \
--map state.report.title=request.title \
--map state.report.markdown=request.body \
--value request.format='"markdown"'
wf draft set-input WS --revision 5 --step publish \
--bindings-file bindings.json
wf draft set-input WS --revision 6 --step publish --clear
```
Replacement is the default and `--bindings-file` is the canonical lossless
form. `--merge` is retained only for compatibility map-only edits. Do not use
it to add literals or when canonical ordering or repeated-source fan-out must
survive. Existing literal bindings are retained during a map-only merge.
`set-output` direction: `text=state.text` means node-local source `local.text`
maps to graph target `state.text`.
Without `--merge`, `set-input`, `set-output`, and `set-workflow-output` replace
the whole map for that step or output scope. Use repeated `--map` flags in one
command for a complete replacement. Use `--merge` only when adding/updating
entries over multiple revisions.
Without `--merge`, `set-input` replaces the whole ordered binding list;
`set-output` and `set-workflow-output` replace their whole maps. Use repeated
flags in one command for a complete replacement. Use `--merge` only for the
compatibility map adapters.
`bind input.title -> local.report.title` is schema-aware and idempotent when
`input.title` is already declared. Bind names both rooted endpoints explicitly.
Use it for repair hints or schema projection. Use
`set-input --merge --map input.title=report.title` when you only need to update
a step input map; that command already implies the local side.
a compatibility step input map; that command already implies the local side.
- `bind_draft`
@@ -16,8 +16,10 @@ validated, runnable deployment.
5. Inspect/patch/validate the workspace until valid.
- Use focused CLI commands (`set-name`, `set-route`, `set-input`, `set-output`)
for common edits.
- `set-input` and `set-output` replace full maps by default; pass `--merge`
only when adding or updating one entry across a later revision.
- `set-input` replaces its ordered canonical binding list; `set-output`
replaces its map. `set-input --merge` is compatibility-only and cannot
add literals or preserve canonical ordering and repeated-source fan-out;
existing literals are retained.
- Before mapping into a new workflow input, state, or output field, prefer
`wf draft bind --from ... --to ...` when it should mirror a capability
local input/output property. It declares the matching schema and merges