feat: accept nested local capability maps

This commit is contained in:
lda
2026-07-22 08:46:15 +07:00 Verified
parent 58e4364012
commit cde0a3c14f
11 changed files with 229 additions and 30 deletions
+3 -4
View File
@@ -57,7 +57,6 @@ from .drafts import (
from .operation_context import WorkflowOperationContext
from .schema_projection import (
project_output_property_to_state_schema,
project_property_to_schema_path,
project_schema_path_to_schema_path,
schema_path_exists,
)
@@ -603,7 +602,7 @@ class WorkflowDraftAuthoringApi:
local_parts = LocalPath.parse(local_path).parts
except ValueError:
continue
if source_root not in {"input", "state"} or len(local_parts) != 1:
if source_root not in {"input", "state"}:
continue
schema_key = "input_schema" if source_root == "input" else "state_schema"
target_schema = (
@@ -613,10 +612,10 @@ class WorkflowDraftAuthoringApi:
)
if schema_path_exists(target_schema, source_parts):
continue
projected = project_property_to_schema_path(
projected = project_schema_path_to_schema_path(
target_schema=target_schema,
source_schema=input_schema,
source_field=local_parts[0],
source_parts=local_parts,
target_parts=source_parts,
allow_existing_equivalent=True,
)
+4 -4
View File
@@ -37,7 +37,6 @@ from wf_core.models.steps import (
from wf_core.models.workflow_refs import WorkflowRef
from .draft_options import (
_parse_map_flags,
_parse_output_map_flags,
_parse_route_flags,
_parse_step_input_map_flags,
@@ -149,11 +148,12 @@ def add_step_from_capability(
`wf draft add capability report_ws --revision 1 --step render
--capability local.report.render --route ok=__end__`
Repeat the flag for multiple bindings:
`--input state.title=title --input state.summary=summary`
Local input targets are rootless node-local paths. Repeat the flag for
multiple bindings:
`--input state.title=report.title --input state.summary=report.summary`
`--bind-output title=state.title --bind-output summary=state.summary`
"""
input_map = _parse_map_flags(input_mapping)
input_map = _parse_step_input_map_flags(input_mapping, option_name="--input")
bind_outputs = _parse_output_map_flags(output_mapping)
routes = _parse_route_flags(route)
context = load_cli_context(ctx)
+9 -2
View File
@@ -64,7 +64,7 @@ def _parse_output_map_flags(
def _parse_step_input_map_flags(
values: list[str] | None, *, option_name: str = "--map"
) -> dict[str, str]:
"""Parse graph-source to bare-local input mappings for one draft step."""
"""Parse graph-source to rootless node-local mappings for one draft step."""
parsed = _parse_assignment_flags(
values,
option_name=option_name,
@@ -74,9 +74,16 @@ def _parse_step_input_map_flags(
if target.startswith("local."):
bare_target = target.removeprefix("local.")
raise typer.BadParameter(
f"{option_name} target must be a bare local field; "
f"{option_name} target must be a rootless node-local path; "
f"use {source}={bare_target}, not {source}={target}"
)
try:
LocalPath.parse(target)
except PathResolutionError as exc:
raise typer.BadParameter(
f"{option_name} target must be a rootless node-local path; "
f"got {target!r}; use report.title or ."
) from exc
return parsed
+14 -3
View File
@@ -399,7 +399,10 @@ def set_step_input_map(
--map entries in one command for a complete replacement. Use --merge only
when adding or updating entries across a later revision.
Targets are bare node-local field names. Use `--map input.text=text`, not
Targets are rootless node-local paths. For example, use
`--map input.title=report.title`, not
`--map input.title=local.report.title`.
Single-field targets remain valid: use `--map input.text=text`, not
`--map input.text=local.text`.
Run `wf draft validate <workspace_id>` after map edits; validation reports
@@ -536,11 +539,17 @@ def bind_draft(
step_id: Annotated[str, typer.Option("--step", help="Draft step id.")],
source_path: Annotated[
str,
typer.Option("--from", help="Source path, for example input.x or local.y."),
typer.Option(
"--from",
help="Explicit source endpoint, such as input.title or local.report.title.",
),
],
target_path: Annotated[
str,
typer.Option("--to", help="Target path, for example local.x or state.y."),
typer.Option(
"--to",
help="Explicit target endpoint, such as local.report.title or state.x.",
),
],
) -> None:
"""Bind a capability step path and project missing schema when needed.
@@ -549,6 +558,8 @@ def bind_draft(
state/output for step outputs. If the workflow schema field already exists,
the command reuses it and updates the step binding. For pure input-map edits
where schema is already known, `wf draft set-input --merge` is also valid.
Bind endpoints are rooted, including nested paths such as
`input.title -> local.report.title`.
Run `wf draft validate <workspace_id>` after this command.
"""
context = load_cli_context(ctx)
+9 -3
View File
@@ -270,10 +270,16 @@ class BindDraftRequest(BaseModel):
revision: int = Field(ge=1, description="Expected current workspace revision.")
step_id: NonEmptyString = Field(description="Capability-backed draft step id.")
source_path: NonEmptyString = Field(
description="Source path, for example input.x or local.y."
description=(
"Explicit source endpoint, for example input.title or "
"local.report.markdown."
)
)
target_path: NonEmptyString = Field(
description="Target path, for example local.x or state.y."
description=(
"Explicit target endpoint, for example local.report.title or "
"state.report.markdown."
)
)
@@ -303,7 +309,7 @@ class AddStepFromCapabilityRequest(BaseModel):
)
input_map: DraftPathMap = Field(
default_factory=dict,
description="Graph source path to node-local target field.",
description="Graph source path to rootless node-local target path.",
)
bind_outputs: DraftPathMap = Field(
default_factory=dict,