290 lines
11 KiB
Markdown
290 lines
11 KiB
Markdown
# Nested Local Draft Bindings Design
|
|
|
|
## Goal
|
|
|
|
Bring focused draft authoring into parity with the canonical runtime model for
|
|
nested node-local paths. Agents and operators should be able to bind structured
|
|
capability inputs and outputs without raw JSON Patch, while the implementation
|
|
reuses canonical path types and one shared JSON Schema projection module.
|
|
|
|
## Problem
|
|
|
|
`LocalPath`, workflow validation, runtime input assembly, runtime output reads,
|
|
and `WorkflowBuilder` already support paths such as `report.title`. Focused
|
|
draft authoring is narrower:
|
|
|
|
- `wf draft bind --from input.title --to local.report.title` rejects the target
|
|
because `bind_draft` requires one local path segment;
|
|
- the inverse output direction rejects nested local sources for the same
|
|
reason;
|
|
- capability-step insertion persists nested input targets but silently skips
|
|
schema projection whenever the local path has more than one segment;
|
|
- CLI help describes step input targets as bare fields rather than canonical
|
|
node-local paths.
|
|
|
|
The limitation is not in the workflow model. It is duplicated path and schema
|
|
handling in the focused authoring layer.
|
|
|
|
## Scope
|
|
|
|
This slice supports nested node-local paths for:
|
|
|
|
- workflow input or state to capability input:
|
|
`input.title -> local.report.title`;
|
|
- capability output to workflow state:
|
|
`local.report.markdown -> state.report.markdown`;
|
|
- capability output to public workflow output, using the existing durable-state
|
|
lowering:
|
|
`local.report.markdown -> output.report.markdown` becomes a node output
|
|
binding to `state.report.markdown` plus a workflow output binding from that
|
|
state path;
|
|
- capability-step input maps such as
|
|
`--map input.title=report.title`, where the node-local root remains implied by
|
|
the existing command interface.
|
|
|
|
The slice also centralizes repeated authoring-time JSON Schema path operations
|
|
and updates CLI/agent-facing path documentation.
|
|
|
|
## Out Of Scope
|
|
|
|
This slice does not:
|
|
|
|
- add literal node-input or workflow-output bindings;
|
|
- replace dictionary maps with a fan-out-safe binding-list interface;
|
|
- add the atomic structured-input assembly helper tracked separately in
|
|
`ISSUES.md`;
|
|
- add step metadata or focused update-step operations;
|
|
- add TypeScript JSON-RPC operations or code generation;
|
|
- change route, revision, deployment, or runtime semantics;
|
|
- implement a general JSON Schema resolver or support remote references;
|
|
- change existing CLI command names or request fields.
|
|
|
|
## Canonical Path Interfaces
|
|
|
|
No new path model is introduced.
|
|
|
|
- `GraphSourcePath` parses workflow-readable `input.*`, `state.*`, and
|
|
`context.*` paths.
|
|
- `LocalPath` parses rootless node-local paths such as `report.title` and the
|
|
whole-payload marker `.`.
|
|
- `wf draft bind` continues using explicit endpoint roots because both sides
|
|
are endpoints: `--from input.title --to local.report.title`.
|
|
- capability-add and set-input maps continue using rootless local targets
|
|
because their interface already implies the node-local side:
|
|
`--map input.title=report.title`.
|
|
|
|
The CLI parser must validate rootless map targets with `LocalPath.parse` instead
|
|
of checking only whether they start with `local.`. Existing single-segment map
|
|
syntax remains valid. Rooted `local.*` map targets remain rejected with an exact
|
|
repair example because changing that syntax is a separate migration.
|
|
|
|
Transport request fields remain strings. Their endpoint role is determined by
|
|
the existing command/method interface, and the semantic authoring module parses
|
|
them through the canonical path classes. Do not create duplicate Pydantic path
|
|
schemas or endpoint unions in RPC models for this slice.
|
|
|
|
## Shared JSON Schema Projection Module
|
|
|
|
Deepen `wf_api.schema_projection` so authoring code does not perform schema
|
|
traversal itself.
|
|
|
|
### Public Operations
|
|
|
|
The module exposes:
|
|
|
|
```python
|
|
def schema_path_exists(
|
|
schema: Mapping[str, Any],
|
|
parts: Sequence[str],
|
|
) -> bool: ...
|
|
|
|
|
|
def project_schema_path_to_schema_path(
|
|
*,
|
|
target_schema: JsonObject,
|
|
source_schema: JsonObject,
|
|
source_parts: tuple[str, ...],
|
|
target_parts: tuple[str, ...],
|
|
allow_existing_equivalent: bool = False,
|
|
) -> JsonObject: ...
|
|
```
|
|
|
|
`project_property_to_schema_path` remains as a compatibility-preserving
|
|
root-property wrapper and delegates to the new operation with
|
|
`source_parts=(source_field,)`. Existing callers and error wording remain
|
|
stable where practical.
|
|
|
|
### Responsibilities
|
|
|
|
The module:
|
|
|
|
1. validates source and target documents with `jsonschema`;
|
|
2. traverses nested object `properties` using path segments supplied by
|
|
canonical path types;
|
|
3. resolves bounded local references used by repository-generated schemas,
|
|
including `#/$defs/...` and `#/definitions/...`;
|
|
4. copies the selected source subschema into the requested target object path;
|
|
5. carries `$defs` and `definitions` blocks needed by copied local references;
|
|
6. validates the projected result;
|
|
7. reports missing source paths, non-object intermediate paths, unsupported
|
|
references, and conflicting target paths precisely.
|
|
|
|
This is authoring-time schema projection, not schema inference. The module does
|
|
not create new Pydantic models mirroring capability schemas and does not
|
|
evaluate arbitrary combinators or remote references. Capability schemas remain
|
|
the documents emitted by `NodeSpec` contracts or Pydantic
|
|
`model_json_schema()`.
|
|
|
|
The duplicate `_schema_path_exists` implementations in `wf_api.drafts` and
|
|
`wf_api.draft_authoring` move into this module. Both callers import the shared
|
|
operation so target-path behavior has one implementation.
|
|
|
|
## Authoring Data Flow
|
|
|
|
### Focused Bind: Workflow Data To Nested Capability Input
|
|
|
|
For `input.title -> local.report.title`:
|
|
|
|
1. revision preflight runs before semantic work;
|
|
2. `GraphSourcePath` parses `input.title`;
|
|
3. `LocalPath` parses rootless `report.title` from the explicit `local.*`
|
|
endpoint;
|
|
4. the capability input schema is selected from its explicit contract or
|
|
Pydantic model;
|
|
5. `project_schema_path_to_schema_path` copies the capability schema at
|
|
`report.title` into the workflow input schema at `title` when absent;
|
|
6. `input_bindings_payload` serializes the canonical nested local target;
|
|
7. the existing revision-checked patch operation persists both changes.
|
|
|
|
Existing workflow source schema paths are reused without projection, preserving
|
|
current idempotent behavior.
|
|
|
|
### Focused Bind: Nested Capability Output To State Or Output
|
|
|
|
For `local.report.markdown -> state.report.markdown`, the capability output
|
|
schema at `report.markdown` is copied to the requested state path and the step
|
|
output binding stores the complete nested `LocalPath`.
|
|
|
|
For `local.report.markdown -> output.report.markdown`, the existing output
|
|
lowering remains unchanged except that the nested capability subschema is used.
|
|
The operation projects that subschema into both state and public output schemas,
|
|
writes the node output into state, and projects workflow output from the same
|
|
state path.
|
|
|
|
### Capability-Step Insertion
|
|
|
|
For `input.title=report.title`:
|
|
|
|
1. `GraphSourcePath.parse` validates the graph source;
|
|
2. `LocalPath.parse` validates the rootless local target;
|
|
3. the complete local parts tuple selects the nested capability input
|
|
subschema;
|
|
4. that subschema is projected into the workflow input/state source path when
|
|
the workflow schema does not already declare it;
|
|
5. the nested input binding is persisted in the same atomic patch as the new
|
|
step and routes.
|
|
|
|
Remove the current `len(local_parts) != 1` skip. A valid nested local path must
|
|
never silently disable schema projection.
|
|
|
|
## Errors And Compatibility
|
|
|
|
Current single-field bindings keep their response shapes and semantics.
|
|
|
|
For current revisions:
|
|
|
|
- an absent nested capability schema path raises a precise `ValueError` naming
|
|
the complete path;
|
|
- traversing through a scalar or otherwise non-object schema raises a precise
|
|
`ValueError` naming the blocking prefix;
|
|
- an unsupported or unresolved reference raises a precise `ValueError` rather
|
|
than silently skipping projection;
|
|
- an existing workflow input/state source path is reused unchanged, preserving
|
|
current bind behavior;
|
|
- output/state projection continues accepting exact equivalent target schemas
|
|
and rejecting incompatible existing targets;
|
|
- no patch is persisted when projection fails.
|
|
|
|
Revision precedence from the preceding slice remains intact: after intrinsic
|
|
request validation, stale requests return `revision_conflict` before path or
|
|
schema errors derived from current workspace/catalog state.
|
|
|
|
Transport fields and response envelopes do not change. RPC, remote-client, and
|
|
CLI layers inherit behavior through existing delegation. Documentation-only
|
|
description changes may clarify nested examples but must not introduce parallel
|
|
request schemas.
|
|
|
|
## Testing
|
|
|
|
### Schema Projection
|
|
|
|
Add focused unit tests for:
|
|
|
|
- inline nested source properties;
|
|
- nested source properties behind Pydantic-style `#/$defs` references;
|
|
- legacy `#/definitions` local references;
|
|
- copied definition blocks remaining resolvable;
|
|
- missing source paths;
|
|
- non-object intermediate source paths;
|
|
- conflicting and equivalent target paths;
|
|
- centralized `schema_path_exists` behavior.
|
|
|
|
### Python Authoring
|
|
|
|
Add API tests for:
|
|
|
|
- input and state binding to nested local targets;
|
|
- nested local output binding to nested state;
|
|
- nested local output binding to public output through state;
|
|
- capability-step insertion with nested input target and missing workflow schema
|
|
projection;
|
|
- current-revision invalid nested capability paths producing clear errors and
|
|
no mutation;
|
|
- stale revision winning over nested-path semantic errors;
|
|
- existing single-segment and idempotent cases remaining unchanged.
|
|
|
|
### Transport And CLI
|
|
|
|
Add delegation/regression tests proving:
|
|
|
|
- JSON-RPC and the remote client preserve nested path strings unchanged;
|
|
- `wf draft bind` accepts explicit rooted nested local endpoints;
|
|
- capability-add and set-input map parsing accept rootless nested local paths;
|
|
- rooted `local.*` map targets remain rejected with a repair example;
|
|
- help and agent instructions distinguish explicit bind endpoints from implied
|
|
local map targets.
|
|
|
|
Compile or validate the resulting draft to prove the stored canonical bindings
|
|
are accepted by the existing workflow model. Runtime nested mapping behavior is
|
|
already covered in `tests/core/test_nested_mappings.py` and should not be
|
|
reimplemented in this slice.
|
|
|
|
## Documentation And Issue State
|
|
|
|
After verification:
|
|
|
|
- check the nested `wf draft bind` limitation;
|
|
- check the capability-step nested projection limitation;
|
|
- check the CLI help/agent instruction limitation;
|
|
- leave atomic structured-input assembly, literal bindings, fan-out maps,
|
|
nested workflow-output source projection, step metadata, and TypeScript parity
|
|
open;
|
|
- add a completed roadmap entry linking to the archived implementation plan;
|
|
- archive the implementation plan under
|
|
`docs/historical/superpowers/plans/`.
|
|
|
|
## Success Criteria
|
|
|
|
- Focused bind supports nested `LocalPath` values in both input and output
|
|
directions.
|
|
- Capability-step insertion projects schemas for nested local input targets
|
|
instead of silently skipping them.
|
|
- Existing single-segment bind and map behavior remains compatible.
|
|
- No new transport request schema or duplicate path type is introduced.
|
|
- Common schema path lookup, local-reference handling, existence checks, and
|
|
projection live in `wf_api.schema_projection`.
|
|
- Resulting drafts compile or validate through the existing canonical workflow
|
|
model.
|
|
- Focused API/RPC/CLI tests, Ruff, formatting, basedpyright, and relevant core
|
|
regressions pass.
|