docs: design atomic step input bindings
This commit is contained in:
@@ -0,0 +1,327 @@
|
|||||||
|
# Atomic Step Input Bindings Design
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Expose the canonical step input-binding list through focused draft authoring so
|
||||||
|
an agent or operator can assemble one structured node input from graph paths and
|
||||||
|
literal values in one revision-checked edit.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The runtime and persisted workflow model already represent node input assembly
|
||||||
|
as an ordered list of `InputPathBinding | InputValueBinding`. Each binding writes
|
||||||
|
one graph-sourced or literal value to a node-local path, and several bindings can
|
||||||
|
therefore build one nested input object.
|
||||||
|
|
||||||
|
Focused draft authoring narrows this list into a source-to-target dictionary.
|
||||||
|
That surface cannot express literal values or one source feeding several local
|
||||||
|
targets. Building a structured input across several focused bind commands also
|
||||||
|
advances the draft revision after every field and can leave an incomplete
|
||||||
|
authoring state between commands.
|
||||||
|
|
||||||
|
The missing concept is not a new runtime binding. It is one atomic authoring
|
||||||
|
operation over the canonical binding list.
|
||||||
|
|
||||||
|
## Terms
|
||||||
|
|
||||||
|
- **Step input binding** is the existing canonical `InputPathBinding` or
|
||||||
|
`InputValueBinding` stored on a node-like step.
|
||||||
|
- **Structured input assembly** is the runtime result of applying several step
|
||||||
|
input bindings to nested node-local targets.
|
||||||
|
- **Binding replacement** replaces the complete input-binding list for one
|
||||||
|
step. It does not merge bindings by source or target.
|
||||||
|
|
||||||
|
Do not introduce a `CompositeBinding` model. Composition is behavior produced
|
||||||
|
by the existing binding list, not a separate persisted domain object.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This slice adds an atomic, focused operation for replacing the complete input
|
||||||
|
bindings of one capability-backed draft step. It supports:
|
||||||
|
|
||||||
|
- several graph sources assembling one nested node-local object;
|
||||||
|
- one graph source feeding several distinct node-local targets;
|
||||||
|
- literal JSON values, including explicit `null`;
|
||||||
|
- nested and whole-payload node-local targets;
|
||||||
|
- authoring-time workflow input/state schema projection from the capability
|
||||||
|
input contract;
|
||||||
|
- local API, JSON-RPC, remote client, MCP, and CLI access.
|
||||||
|
|
||||||
|
The stored workflow shape, core runtime behavior, and canonical path models do
|
||||||
|
not change.
|
||||||
|
|
||||||
|
## Out Of Scope
|
||||||
|
|
||||||
|
This slice does not:
|
||||||
|
|
||||||
|
- add incremental add/update/remove semantics to the canonical operation;
|
||||||
|
- add revision amend, rebase, fast-forward, or history semantics;
|
||||||
|
- change step output bindings or workflow output bindings;
|
||||||
|
- solve output fan-out or literal workflow outputs;
|
||||||
|
- add focused step metadata or update-step operations;
|
||||||
|
- add TypeScript JSON-RPC parity or code generation;
|
||||||
|
- introduce a structured template language such as `$path` expressions;
|
||||||
|
- implement general JSON Schema reference resolution or schema assignability.
|
||||||
|
|
||||||
|
## Canonical Interface
|
||||||
|
|
||||||
|
Add the following method to `WorkflowApiSurface` and its local implementation:
|
||||||
|
|
||||||
|
```python
|
||||||
|
async def set_step_input_bindings(
|
||||||
|
*,
|
||||||
|
workspace_id: str,
|
||||||
|
revision: int,
|
||||||
|
step_id: str,
|
||||||
|
bindings: Sequence[InputBinding],
|
||||||
|
) -> dict[str, Any]: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Expose the same request through:
|
||||||
|
|
||||||
|
- JSON-RPC method
|
||||||
|
`workflow.draft_workspaces.set_step_input_bindings`;
|
||||||
|
- `RpcWorkflowApiClient`;
|
||||||
|
- the MCP workflow surface.
|
||||||
|
|
||||||
|
Transport models reuse `wf_core.models.steps.InputBinding`. They must not define
|
||||||
|
parallel path/value binding models.
|
||||||
|
|
||||||
|
The operation replaces the complete ordered list. It preserves caller order in
|
||||||
|
the stored draft. Repeated graph source paths are valid when their local targets
|
||||||
|
are distinct. Equal or ancestor/descendant local targets overlap and are
|
||||||
|
rejected, regardless of list order.
|
||||||
|
|
||||||
|
Example request bindings:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"path": "state.report.title", "target": "request.title"},
|
||||||
|
{"path": "state.report.markdown", "target": "request.body"},
|
||||||
|
{"value": "markdown", "target": "request.format"}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Revision And Mutation Semantics
|
||||||
|
|
||||||
|
The operation follows the established semantic-authoring precedence:
|
||||||
|
|
||||||
|
1. Validate the request envelope and canonical binding shapes.
|
||||||
|
2. Load the workspace and compare the expected revision.
|
||||||
|
3. Return the canonical `revision_conflict` result immediately when stale.
|
||||||
|
4. Resolve the current step and capability contract.
|
||||||
|
5. Validate binding semantics and compute schema projections.
|
||||||
|
6. Replace the binding list and projected schemas through one JSON Patch call.
|
||||||
|
7. Let the mutation-time revision guard protect against races after preflight.
|
||||||
|
|
||||||
|
No partial patch is persisted when any binding fails. A changed replacement
|
||||||
|
creates exactly one new draft revision. After revision and semantic validation,
|
||||||
|
an exact replacement whose canonical bindings and projected schemas equal the
|
||||||
|
stored values returns the current workspace summary without advancing the
|
||||||
|
revision.
|
||||||
|
|
||||||
|
## Semantic Validation
|
||||||
|
|
||||||
|
The authoring operation resolves the capability-backed step and validates the
|
||||||
|
complete replacement before mutation.
|
||||||
|
|
||||||
|
### Local Targets
|
||||||
|
|
||||||
|
Every binding target must resolve against the capability input schema. Nested
|
||||||
|
targets may traverse bounded local `#/$defs/...` or `#/definitions/...`
|
||||||
|
references. The whole-payload target `.` addresses the complete capability
|
||||||
|
input schema.
|
||||||
|
|
||||||
|
Targets must not overlap. For example, `request` and `request.title` cannot
|
||||||
|
appear together because the runtime would otherwise make the result depend on
|
||||||
|
write order. Two bindings with the same target are also overlapping. The same
|
||||||
|
source may feed `request.title` and `audit.title`.
|
||||||
|
|
||||||
|
### Path Bindings
|
||||||
|
|
||||||
|
`InputPathBinding.path` continues accepting canonical workflow-readable
|
||||||
|
`input.*`, `state.*`, and `context.*` paths.
|
||||||
|
|
||||||
|
For `input.*` and `state.*` sources:
|
||||||
|
|
||||||
|
- reuse an already-declared workflow schema path;
|
||||||
|
- when the source path is missing, project its schema from the corresponding
|
||||||
|
capability-input target schema;
|
||||||
|
- apply every projection to staged schemas and commit them with the binding
|
||||||
|
replacement.
|
||||||
|
|
||||||
|
Existing source schemas are not subjected to a new assignability algorithm.
|
||||||
|
They retain current bind behavior and remain subject to draft/runtime
|
||||||
|
validation. `context.*` bindings are valid but do not project workflow schemas.
|
||||||
|
|
||||||
|
### Literal Bindings
|
||||||
|
|
||||||
|
`InputValueBinding.value` accepts any JSON-compatible value, including `null`,
|
||||||
|
arrays, and objects. Validate a literal against the capability schema selected
|
||||||
|
by its local target before mutation. A literal targeting `.` must be an object
|
||||||
|
that satisfies the complete capability input schema because the runtime whole
|
||||||
|
payload operation requires a mapping.
|
||||||
|
|
||||||
|
## Shared JSON Schema Operations
|
||||||
|
|
||||||
|
Deepen `wf_api.schema_projection` rather than adding traversal to draft
|
||||||
|
authoring. Add one reusable schema-path selection/validation operation that:
|
||||||
|
|
||||||
|
- selects inline nested schemas;
|
||||||
|
- follows bounded local `$defs` and legacy `definitions` references;
|
||||||
|
- retains enough root definition context to validate selected fragments;
|
||||||
|
- rejects remote, unresolved, cyclic, and non-object intermediate references
|
||||||
|
precisely;
|
||||||
|
- supports the existing projection helpers and literal validation.
|
||||||
|
|
||||||
|
The exact private/public helper split is an implementation choice, but schema
|
||||||
|
path traversal, local-reference handling, projection, and literal validation
|
||||||
|
must remain localized in `wf_api.schema_projection`.
|
||||||
|
|
||||||
|
## CLI Design
|
||||||
|
|
||||||
|
Keep the existing command name:
|
||||||
|
|
||||||
|
```text
|
||||||
|
wf draft set-input WORKSPACE --revision N --step STEP ...
|
||||||
|
```
|
||||||
|
|
||||||
|
Preferred canonical replacement forms are:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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"'
|
||||||
|
```
|
||||||
|
|
||||||
|
and:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wf draft set-input WS \
|
||||||
|
--revision 4 \
|
||||||
|
--step publish \
|
||||||
|
--bindings-file bindings.json
|
||||||
|
```
|
||||||
|
|
||||||
|
CLI behavior:
|
||||||
|
|
||||||
|
- repeatable `--map SOURCE=LOCAL_TARGET` creates path bindings;
|
||||||
|
- repeatable `--value LOCAL_TARGET=JSON` creates literal bindings;
|
||||||
|
- `--bindings-file` accepts the exact canonical JSON array;
|
||||||
|
- `--bindings-file` is mutually exclusive with `--map` and `--value`;
|
||||||
|
- `--clear` explicitly replaces the list with `[]`;
|
||||||
|
- no bindings and no `--clear` is an input error;
|
||||||
|
- the canonical replacement path does not support `--merge`.
|
||||||
|
|
||||||
|
`--bindings-file` preserves exact list order. Convenience flags serialize path
|
||||||
|
bindings in `--map` order followed by literal bindings in `--value` order.
|
||||||
|
Because overlapping targets are rejected, this cross-kind ordering cannot
|
||||||
|
change the assembled payload.
|
||||||
|
|
||||||
|
The existing map-only API/RPC operation and its merge behavior remain as a
|
||||||
|
compatibility adapter for real callers. Existing CLI `--merge` may continue to
|
||||||
|
delegate to that adapter only for map-only requests; it must reject combinations
|
||||||
|
with `--value`, `--bindings-file`, or `--clear`. New help and agent instructions
|
||||||
|
describe replacement as the preferred behavior.
|
||||||
|
|
||||||
|
Current bindings remain exportable without another endpoint:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
wf draft inspect WS --include-draft |
|
||||||
|
jq '.draft.steps.publish.input' > bindings.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Contract
|
||||||
|
|
||||||
|
Semantic errors identify the binding index and complete canonical path where
|
||||||
|
possible. Representative messages are:
|
||||||
|
|
||||||
|
- `bindings[1].target 'request.missing' is not declared by capability ...`;
|
||||||
|
- `bindings[0].target 'request' overlaps bindings[1].target 'request.title'`;
|
||||||
|
- `bindings[2].value does not satisfy the schema at 'request.format'`;
|
||||||
|
- `bindings[0].path 'input.report.title' cannot be projected from target ...`.
|
||||||
|
|
||||||
|
Malformed canonical binding objects fail request-envelope validation. After
|
||||||
|
that validation, stale revision wins over missing steps, unknown capabilities,
|
||||||
|
schema lookup, overlap, and literal-value errors. All failures leave the draft
|
||||||
|
document and revision unchanged.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Schema Projection
|
||||||
|
|
||||||
|
Add focused tests for:
|
||||||
|
|
||||||
|
- selecting inline and nested schema paths;
|
||||||
|
- selecting paths through `$defs` and `definitions` references;
|
||||||
|
- preserving definition context during literal validation;
|
||||||
|
- valid and invalid literal scalars, objects, arrays, and explicit `null`;
|
||||||
|
- remote, unresolved, cyclic, and non-object intermediate references.
|
||||||
|
|
||||||
|
### Python Authoring
|
||||||
|
|
||||||
|
Add API tests for:
|
||||||
|
|
||||||
|
- several path bindings assembling one nested capability input;
|
||||||
|
- path and literal bindings in one replacement;
|
||||||
|
- one source fanning out to several targets;
|
||||||
|
- explicit `null` and whole-payload bindings;
|
||||||
|
- stable binding order in the stored draft;
|
||||||
|
- input and state schema projection across several bindings;
|
||||||
|
- context bindings without schema projection;
|
||||||
|
- missing, duplicate, and ancestor/descendant targets;
|
||||||
|
- invalid literals;
|
||||||
|
- stale revision precedence and no mutation on every failure class;
|
||||||
|
- replacement rather than merge semantics;
|
||||||
|
- compatibility of the existing map-only operation.
|
||||||
|
|
||||||
|
Compile the resulting draft and execute one representative workflow to prove
|
||||||
|
the runtime receives the intended nested input object.
|
||||||
|
|
||||||
|
### Transport And CLI
|
||||||
|
|
||||||
|
Add tests proving:
|
||||||
|
|
||||||
|
- JSON-RPC, remote client, and MCP preserve canonical path/value unions and
|
||||||
|
binding order;
|
||||||
|
- CLI `--map` and `--value` compose one replacement request;
|
||||||
|
- `--bindings-file` round-trips an exported inspected list;
|
||||||
|
- `--clear` is explicit and an empty accidental invocation fails;
|
||||||
|
- canonical replacement rejects `--merge` combinations;
|
||||||
|
- the legacy map-only merge path remains operational;
|
||||||
|
- expected errors remain compact through local and remote CLI targets.
|
||||||
|
|
||||||
|
## Documentation And Issue State
|
||||||
|
|
||||||
|
After verification:
|
||||||
|
|
||||||
|
- check the atomic structured node-input assembly issue;
|
||||||
|
- check the focused literal node-input binding issue;
|
||||||
|
- leave input/output fan-out map loss open unless the implementation also
|
||||||
|
migrates every lossy reader and writer to canonical lists;
|
||||||
|
- leave workflow-output literals, nested workflow-output projection, focused
|
||||||
|
step updates, and TypeScript parity open;
|
||||||
|
- update `wf_cli` and agent skill documentation with replacement, literal, and
|
||||||
|
export examples;
|
||||||
|
- add a completed roadmap entry linking to the archived implementation plan;
|
||||||
|
- archive the implementation plan under
|
||||||
|
`docs/historical/superpowers/plans/`.
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
- One focused operation replaces a step's complete canonical input-binding list
|
||||||
|
in one revision.
|
||||||
|
- Structured nested assembly, path fan-out, literal values, explicit `null`,
|
||||||
|
and whole-payload targets use the existing runtime model unchanged.
|
||||||
|
- All local targets are validated against the capability input schema before
|
||||||
|
mutation.
|
||||||
|
- Missing input/state source schemas are projected atomically with the binding
|
||||||
|
replacement.
|
||||||
|
- Existing map-only callers retain their current behavior through a compatibility
|
||||||
|
adapter, while new callers use canonical bindings.
|
||||||
|
- No new persisted binding type or template language is introduced.
|
||||||
|
- Focused API/RPC/MCP/CLI tests, Ruff, formatting, basedpyright, compile, and
|
||||||
|
runtime regressions pass.
|
||||||
Reference in New Issue
Block a user