docs: complete nested local draft bindings
This commit is contained in:
@@ -8,10 +8,10 @@
|
||||
|
||||
## Draft data-shaping parity
|
||||
|
||||
- [ ] `wf draft bind` rejects nested node-local targets such as
|
||||
- [x] `wf draft bind` rejects nested node-local targets such as
|
||||
`local.report.title`, even though canonical `LocalPath`, the runtime binding
|
||||
resolver, and `WorkflowBuilder` support nested local paths.
|
||||
- [ ] Capability-step authoring persists nested input targets but silently skips
|
||||
- [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
|
||||
@@ -29,7 +29,7 @@
|
||||
- [ ] Workflow output schema projection skips nested sources such as
|
||||
`state.report.title`, leaving callers to patch the output schema manually even
|
||||
when the nested source schema is already declared.
|
||||
- [ ] CLI help and agent instructions describe step-input targets as bare local
|
||||
- [x] CLI help and agent instructions describe step-input targets as bare local
|
||||
fields and do not document the nested composition behavior already supported
|
||||
by the canonical runtime model.
|
||||
|
||||
|
||||
@@ -549,6 +549,11 @@ clear operator feedback before adding more architecture.
|
||||
`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).
|
||||
- Completed: focused draft bind and capability-step insertion now preserve
|
||||
nested node-local paths and project nested capability schemas through one
|
||||
shared bounded JSON Schema path module. CLI and agent guidance distinguish
|
||||
rooted bind endpoints from implied rootless map targets. Implementation:
|
||||
[`nested local draft bindings`](historical/superpowers/plans/2026-07-22-nested-local-draft-bindings.md).
|
||||
- Completed: `wf draft set-input` and `wf draft set-output` now accept
|
||||
`--merge`, preserving existing bindings when agents split map edits across
|
||||
multiple revisions.
|
||||
|
||||
+5
-5
@@ -555,7 +555,7 @@
|
||||
- Closes only: nested bind rejection, capability-add nested projection skip, and stale CLI/agent guidance.
|
||||
- Leaves open: structured-input assembly, literals, fan-out maps, nested workflow-output source projection, metadata/update-step, and TypeScript parity.
|
||||
|
||||
- [ ] **Step 1: Update CLI and agent-facing examples**
|
||||
- [x] **Step 1: Update CLI and agent-facing examples**
|
||||
|
||||
Replace “bare field” wording with “rootless node-local path” in all three live user-facing documents. Include both forms together:
|
||||
|
||||
@@ -569,7 +569,7 @@
|
||||
|
||||
Explain that `wf draft bind` names both rooted endpoints, while `set-input` and capability-add already imply the local side and therefore accept rootless targets. Do not claim support for literals, fan-out, or atomic object assembly.
|
||||
|
||||
- [ ] **Step 2: Update issue and roadmap state precisely**
|
||||
- [x] **Step 2: Update issue and roadmap state precisely**
|
||||
|
||||
In `ISSUES.md`, check only these three entries:
|
||||
|
||||
@@ -585,7 +585,7 @@
|
||||
historical/superpowers/plans/2026-07-22-nested-local-draft-bindings.md
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run the complete focused verification matrix**
|
||||
- [x] **Step 3: Run the complete focused verification matrix**
|
||||
|
||||
Run:
|
||||
|
||||
@@ -599,7 +599,7 @@
|
||||
|
||||
Expected: all focused tests and static checks pass. The pytest count may increase as tests are added; report exact totals rather than copying a planned count.
|
||||
|
||||
- [ ] **Step 4: Run an independent two-axis review**
|
||||
- [x] **Step 4: Run an independent two-axis review**
|
||||
|
||||
Use the `requesting-code-review` skill against the implementation start commit. Review:
|
||||
|
||||
@@ -608,7 +608,7 @@
|
||||
|
||||
Fix Critical and Important findings, rerun affected focused tests, and record any intentional Minor deferrals in the final report.
|
||||
|
||||
- [ ] **Step 5: Archive the completed plan and commit documentation**
|
||||
- [x] **Step 5: Archive the completed plan and commit documentation**
|
||||
|
||||
After all checks pass:
|
||||
|
||||
+12
-4
@@ -328,9 +328,11 @@ wf draft compile concat_ws
|
||||
```
|
||||
|
||||
`set-input` maps graph source paths to node-local input fields:
|
||||
`input.text=text` means `input.text -> local.text`. Targets are bare
|
||||
node-local field names: write `--map input.text=text`, not
|
||||
`--map input.text=local.text`.
|
||||
`input.title=report.title` means
|
||||
`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.
|
||||
|
||||
`set-output` maps node-local output fields to workflow state paths:
|
||||
`text=state.text` means `local.text -> state.text`.
|
||||
@@ -360,9 +362,15 @@ use `input.*` or `state.*` to `local.*` for step inputs, and `local.*` to
|
||||
wf draft bind concat_ws --revision 9 --step call --from local.value --to state.value
|
||||
wf draft bind concat_ws --revision 9 --step call --from input.text --to local.text
|
||||
wf draft bind concat_ws --revision 9 --step call --from local.result --to output.result
|
||||
wf draft bind report_ws --revision 2 --step render --from input.title --to local.report.title
|
||||
wf draft set-input report_ws --revision 3 --step render --map input.title=report.title
|
||||
wf draft validate concat_ws
|
||||
```
|
||||
|
||||
`bind` names both endpoints, so nested local paths use the explicit `local.`
|
||||
root. `set-input` and `draft add capability --input` already imply the local
|
||||
side and therefore use rootless targets such as `report.title`.
|
||||
|
||||
If the workflow schema field already exists, `bind` reuses it and only updates
|
||||
the step binding. Use `set-input --merge` for pure input-map edits when no
|
||||
schema projection is needed.
|
||||
@@ -383,7 +391,7 @@ wf draft set-workflow-output report_ws --revision 6 --map state.markdown=markdow
|
||||
|
||||
The command combines two common edits:
|
||||
|
||||
- It copies the selected capability local field schema into the workflow input,
|
||||
- It copies the selected capability local path schema into the workflow input,
|
||||
state, or output schema at the graph path.
|
||||
- It merges the matching step input or output binding.
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ wf draft set-name <workspace_id> --revision <n> --name <name>
|
||||
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.text=text
|
||||
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> --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
|
||||
@@ -51,9 +51,9 @@ wf draft set-workflow-output <workspace_id> --revision <n> --merge --map state.o
|
||||
wf draft branch <workspace_id> --revision <n> --step <step_id> --route ok=__end__ --route error=fail
|
||||
wf draft handle <workspace_id> --revision <n> --to fail --branch lookup:error --branch transform:error
|
||||
wf draft compile <workspace_id>
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from local.<field> --to state.<field>
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from input.<field> --to local.<field>
|
||||
wf draft add capability <workspace_id> --revision <n> --step <step_id> --capability <qualified_name> --from-step <prev> --from-outcome ok --route ok=__end__ --route error=fail --input input.text=text --bind-output result=state.result
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from local.report.markdown --to state.report.markdown
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from input.title --to local.report.title
|
||||
wf draft add capability <workspace_id> --revision <n> --step <step_id> --capability <qualified_name> --from-step <prev> --from-outcome ok --route ok=__end__ --route error=fail --input input.title=report.title --bind-output result=state.result
|
||||
wf draft add interrupt <workspace_id> --revision <n> --step review --kind issue_review \
|
||||
--request-schema-file request.schema.json --resume-schema-file resume.schema.json \
|
||||
--outcome submitted --outcome cancelled --route submitted=next --route cancelled=revise
|
||||
@@ -80,6 +80,11 @@ 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.
|
||||
|
||||
`wf draft bind` names both endpoints explicitly, so local paths keep the
|
||||
`local.` root. `set-input` and `draft add capability --input` already imply the
|
||||
local side, so their targets are rootless paths: write
|
||||
`input.title=report.title`, not `input.title=local.report.title`.
|
||||
|
||||
`wf draft set-workflow-output` projects missing public output schema fields for
|
||||
single-field `input.*` and `state.*` sources. Prefer it for final workflow
|
||||
outputs; use `wf draft bind --from local.x --to output.y` when the source is a
|
||||
|
||||
@@ -99,7 +99,7 @@ CLI equivalents:
|
||||
```bash
|
||||
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.text=text
|
||||
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> --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
|
||||
@@ -108,9 +108,9 @@ wf draft set-workflow-output <workspace_id> --revision <n> --merge --map state.o
|
||||
wf draft branch <workspace_id> --revision <n> --step <step_id> --route ok=__end__ --route error=fail
|
||||
wf draft handle <workspace_id> --revision <n> --to fail --branch lookup:error --branch transform:error
|
||||
wf draft compile <workspace_id>
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from local.<field> --to state.<field>
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from input.<field> --to local.<field>
|
||||
wf draft add capability <workspace_id> --revision <n> --step <step_id> --capability <qualified_name> --from-step <prev> --from-outcome ok --route ok=__end__ --route error=fail --input input.text=text --bind-output result=state.result
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from local.report.markdown --to state.report.markdown
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from input.title --to local.report.title
|
||||
wf draft add capability <workspace_id> --revision <n> --step <step_id> --capability <qualified_name> --from-step <prev> --from-outcome ok --route ok=__end__ --route error=fail --input input.title=report.title --bind-output result=state.result
|
||||
wf draft add interrupt <workspace_id> --revision <n> --step review --kind issue_review --request-schema-file request.schema.json --resume-schema-file resume.schema.json --outcome submitted --outcome cancelled --route submitted=next --route cancelled=revise
|
||||
wf draft add when <workspace_id> --revision <n> --step decide --condition-file condition.json --then next --otherwise revise
|
||||
```
|
||||
@@ -121,9 +121,10 @@ wf draft add when <workspace_id> --revision <n> --step decide --condition-file c
|
||||
For single-field `input.*` and `state.*` sources, missing public output schema
|
||||
fields are projected automatically from the source schema.
|
||||
|
||||
`set-input` direction: `input.text=text` means graph source `input.text` maps to
|
||||
node-local target `local.text`. Targets are bare node-local field names; never
|
||||
prefix the target with `local.`.
|
||||
`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.
|
||||
|
||||
`set-output` direction: `text=state.text` means node-local source `local.text`
|
||||
maps to graph target `state.text`.
|
||||
@@ -133,10 +134,11 @@ 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.
|
||||
|
||||
`bind input.x -> local.x` is schema-aware and idempotent when `input.x` is
|
||||
already declared. Use it for repair hints or schema projection. Use
|
||||
`set-input --merge --map input.x=x` when you only need to update a step input
|
||||
map.
|
||||
`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.
|
||||
|
||||
- `bind_draft`
|
||||
|
||||
@@ -152,9 +154,9 @@ map.
|
||||
workflow boundary.
|
||||
|
||||
```bash
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from local.<field> --to state.<field>
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from input.<field> --to local.<field>
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from local.<field> --to output.<field>
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from local.report.markdown --to state.report.markdown
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from input.title --to local.report.title
|
||||
wf draft bind <workspace_id> --revision <n> --step <step_id> --from local.report.markdown --to output.report.markdown
|
||||
wf draft validate <workspace_id>
|
||||
```
|
||||
|
||||
@@ -172,9 +174,9 @@ wf draft validate <workspace_id>
|
||||
unknown route entries and add one route for each missing declared outcome. It
|
||||
still requires explicit choices; if you do not
|
||||
know a map, inspect the capability or run validation rather than guessing.
|
||||
Explicit top-level `--input input.x=x` and `--input state.x=x` mappings
|
||||
project the corresponding workflow input/state schema fields from the
|
||||
capability input schema.
|
||||
Explicit `--input input.title=report.title` and
|
||||
`--input state.title=report.title` mappings project the corresponding
|
||||
workflow input/state schema paths from the nested capability input schema.
|
||||
|
||||
```bash
|
||||
wf draft add capability <workspace_id> --revision <n> --step <step_id> --capability <qualified_name> --from-step <prev> --from-outcome ok --route ok=__end__ --route error=fail --input input.text=text --input input.other=other --bind-output result=state.result --bind-output title=state.title
|
||||
|
||||
@@ -604,6 +604,10 @@ class WorkflowDraftAuthoringApi:
|
||||
continue
|
||||
if source_root not in {"input", "state"}:
|
||||
continue
|
||||
if not local_parts:
|
||||
# `.` binds the whole graph value to the whole node input. It has
|
||||
# no capability-property path from which to project one field.
|
||||
continue
|
||||
schema_key = "input_schema" if source_root == "input" else "state_schema"
|
||||
target_schema = (
|
||||
projected_input_schema
|
||||
|
||||
@@ -1461,6 +1461,39 @@ async def test_add_step_from_capability_projects_nested_local_input(
|
||||
assert validated["status"] == "valid", validated["diagnostics"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_step_from_capability_preserves_whole_payload_input(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
api, service, authoring = _draft_api(
|
||||
FileWorkflowArtifactStore(tmp_path / "whole_payload_capability_input"),
|
||||
register_echo=True,
|
||||
)
|
||||
service.register_specs("demo.personal", _nested_report)
|
||||
draft = _nested_report_draft()
|
||||
draft["steps"] = {}
|
||||
draft["routes"] = {}
|
||||
await api.create_draft_workspace(workspace_id="whole_payload", draft=draft)
|
||||
|
||||
result = await authoring.add_step_from_capability(
|
||||
workspace_id="whole_payload",
|
||||
revision=1,
|
||||
step_id="render",
|
||||
capability_name="demo.personal.nested_report",
|
||||
routes={"ok": "__end__"},
|
||||
input_map={"input.payload": "."},
|
||||
bind_outputs={},
|
||||
)
|
||||
workspace = await api.get_draft_workspace(
|
||||
workspace_id="whole_payload", include_draft=True
|
||||
)
|
||||
|
||||
assert result["revision"] == 2
|
||||
assert workspace["draft"]["steps"]["render"]["input"] == [
|
||||
{"target": ".", "path": "input.payload"}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_step_from_capability_rejects_existing_step_id(
|
||||
tmp_path: Path,
|
||||
|
||||
Reference in New Issue
Block a user