fix: smooth challenge-driven cli ux gaps
This commit is contained in:
@@ -271,6 +271,10 @@ stable.
|
||||
- Completed: canonical TOML path strings replace structural JSON path objects
|
||||
in workflow drafts. Paths now emit as `"input.text"`, `"state.echoed"`,
|
||||
`"message"` (local) instead of `{"root": "input", "parts": ["text"]}`.
|
||||
- Completed: challenge-driven CLI UX fixes now provide exact available
|
||||
deployment binding suggestions, reject bare `--bind-output` state targets
|
||||
before RPC with compact guidance, and accept `wf schema --full` as an alias
|
||||
for `--verbose`.
|
||||
|
||||
## Historical References
|
||||
|
||||
|
||||
@@ -134,6 +134,18 @@ availability, durable run counts/latest run, admin counts, auth record count,
|
||||
and desired registry count when the target exposes those surfaces. It does not
|
||||
return auth payload values, trace entries, or checkpoint state.
|
||||
|
||||
Schema discovery:
|
||||
|
||||
```bash
|
||||
wf schema
|
||||
wf schema draft
|
||||
wf schema raw --verbose
|
||||
wf schema raw --full
|
||||
```
|
||||
|
||||
`--full` is an alias for `--verbose`; docs use `--verbose` as the canonical
|
||||
flag.
|
||||
|
||||
### Diagnose A Source
|
||||
|
||||
Use `wf source diagnose <source_id>` to inspect source health before calling
|
||||
@@ -477,6 +489,9 @@ wf artifact create-from-plan workflow.plan.json \
|
||||
|
||||
Prefer draft workspaces for iterative authoring. Use `create-from-plan` when a
|
||||
compiler, fixture, or advanced client already has a complete raw workflow plan.
|
||||
Artifact save responses include `required_logical_sources` and may include
|
||||
`suggested_bindings`. Copy suggested bindings into `wf deploy save --binding`
|
||||
when they are present; otherwise choose the concrete source/account explicitly.
|
||||
|
||||
Delete an unreferenced artifact version:
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ Use public CLI surfaces before broader documentation or implementation search:
|
||||
6. `wf explain <diagnostic-code>` after validation failures
|
||||
|
||||
Use `wf schema <name> --verbose` only when the complete JSON Schema is required;
|
||||
the default compact outline is preferred for agent context.
|
||||
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
|
||||
@@ -121,13 +122,15 @@ top-level `compiled_plan` key from the CLI output.
|
||||
- Use `wf schema draft`, `wf schema raw`, or `wf schema <Component>` for compact
|
||||
JSON guidance before authoring.
|
||||
- Add `--verbose` only when a complete JSON Schema document is required; it may
|
||||
be large.
|
||||
be large. `--full` is an alias if you already tried that spelling.
|
||||
- Prefer `wf schema` over searching tests or implementation code for draft/raw
|
||||
plan shape.
|
||||
- Treat compact schema output as authoring guidance; use validation commands as
|
||||
the source of truth for a concrete document.
|
||||
- If public commands and supplied skills are insufficient, report the exact
|
||||
blocker instead of guessing undocumented fields.
|
||||
- When artifact save returns `suggested_bindings`, copy those values into
|
||||
`wf deploy save --binding`; otherwise choose the concrete source explicitly.
|
||||
- `status: invalid` from a draft edit is not always a command failure. Inspect
|
||||
diagnostics and continue repairing the same workspace unless the command
|
||||
reports a conflict or malformed patch.
|
||||
|
||||
@@ -69,6 +69,19 @@ wf deploy validate <deployment_id>
|
||||
wf run start <deployment_id> --input-file input.json
|
||||
```
|
||||
|
||||
If artifact save returns `suggested_bindings`, include each suggestion as a
|
||||
deployment binding:
|
||||
|
||||
```bash
|
||||
wf deploy save <deployment_id> \
|
||||
--artifact <artifact_id> \
|
||||
--version 1 \
|
||||
--binding local.report=local.report
|
||||
```
|
||||
|
||||
If no suggestion is present, do not guess an account-like source binding; inspect
|
||||
sources and choose the concrete source explicitly.
|
||||
|
||||
## Object Model
|
||||
|
||||
- **Source**: owner of capabilities, such as `wf.std` or `everything.default`.
|
||||
|
||||
+21
-4
@@ -199,7 +199,10 @@ class WorkflowArtifactApi:
|
||||
"version": workflow_artifact.version,
|
||||
"saved": True,
|
||||
"required_logical_sources": required_sources,
|
||||
"suggested_bindings": _suggested_self_bindings(required_sources),
|
||||
"suggested_bindings": _suggested_self_bindings(
|
||||
required_sources,
|
||||
self.context.specs.capability_sources,
|
||||
),
|
||||
}
|
||||
|
||||
async def create_artifact_from_workspace(
|
||||
@@ -302,9 +305,23 @@ class WorkflowArtifactApi:
|
||||
}
|
||||
|
||||
|
||||
def _suggested_self_bindings(required_sources: Sequence[str]) -> dict[str, str]:
|
||||
"""Suggest local bindings for external sources that deploy to themselves."""
|
||||
return {}
|
||||
def _suggested_self_bindings(
|
||||
required_sources: Sequence[str],
|
||||
sources: Mapping[str, CapabilitySource],
|
||||
) -> dict[str, str]:
|
||||
"""Suggest only exact, enabled concrete sources as deployment bindings.
|
||||
|
||||
Ambiguous logical sources such as ``drive`` with multiple concrete accounts
|
||||
must stay unsuggested. Exact ids such as ``local.report`` are safe because
|
||||
the same source is already present in the active inventory.
|
||||
"""
|
||||
return {
|
||||
source_id: source_id
|
||||
for source_id in required_sources
|
||||
if (source := sources.get(source_id)) is not None
|
||||
and source.enabled
|
||||
and source.policy.binding_required
|
||||
}
|
||||
|
||||
|
||||
def _binding_required_sources(
|
||||
|
||||
@@ -10,6 +10,7 @@ from wf_cli.context import load_cli_context_from_typer as load_cli_context
|
||||
from wf_cli.formats import ListOutputFormat, emit_list_payload
|
||||
from wf_cli.io import CliInputError, emit_json, parse_bindings, parse_json_value
|
||||
from wf_cli.remote_errors import run_cli_operation
|
||||
from wf_core.paths import LocalPath, PathResolutionError, StatePath
|
||||
|
||||
|
||||
def _parse_assignment_flags(
|
||||
@@ -37,6 +38,30 @@ def _parse_map_flags(values: list[str] | None) -> dict[str, str]:
|
||||
)
|
||||
|
||||
|
||||
def _parse_output_map_flags(values: list[str] | None) -> dict[str, str]:
|
||||
parsed = _parse_assignment_flags(
|
||||
values,
|
||||
option_name="--bind-output",
|
||||
expected="LOCAL_OUTPUT=STATE_TARGET",
|
||||
)
|
||||
for local_output, state_target in parsed.items():
|
||||
try:
|
||||
LocalPath.parse(local_output)
|
||||
except PathResolutionError as exc:
|
||||
raise typer.BadParameter(
|
||||
f"--bind-output source {local_output!r} must be a node-local "
|
||||
"output path such as value or ."
|
||||
) from exc
|
||||
try:
|
||||
StatePath.parse(state_target)
|
||||
except PathResolutionError as exc:
|
||||
raise typer.BadParameter(
|
||||
f"--bind-output target {state_target!r} must be a state path "
|
||||
"such as state.value"
|
||||
) from exc
|
||||
return parsed
|
||||
|
||||
|
||||
def _parse_route_flags(values: list[str] | None) -> dict[str, str]:
|
||||
return _parse_assignment_flags(
|
||||
values,
|
||||
@@ -396,7 +421,7 @@ def add_step_from_capability(
|
||||
want, then run `wf draft validate <workspace_id>`.
|
||||
"""
|
||||
input_map = _parse_map_flags(input_mapping)
|
||||
bind_outputs = _parse_map_flags(output_mapping)
|
||||
bind_outputs = _parse_output_map_flags(output_mapping)
|
||||
routes = _parse_route_flags(route)
|
||||
context = load_cli_context(ctx)
|
||||
emit_json(
|
||||
|
||||
@@ -19,6 +19,7 @@ def schema_command(
|
||||
verbose: bool = typer.Option(
|
||||
False,
|
||||
"--verbose",
|
||||
"--full",
|
||||
"-v",
|
||||
help="Print complete valid JSON Schema; output may be large.",
|
||||
),
|
||||
|
||||
@@ -185,6 +185,32 @@ async def test_create_artifact_from_plan_saves_with_observed_node_specs(
|
||||
assert saved.id == "echo"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_artifact_from_workspace_suggests_exact_available_source_binding(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_binding_hint")
|
||||
api, service = _artifact_api(artifact_store, register_echo=True)
|
||||
from wf_api.drafts import WorkflowDraftApi
|
||||
|
||||
drafts_api = WorkflowDraftApi(context_from_service(service))
|
||||
await drafts_api.create_draft_workspace(
|
||||
workspace_id="echo_ws",
|
||||
draft=_echo_draft(),
|
||||
)
|
||||
|
||||
result = await api.create_artifact_from_workspace(
|
||||
workspace_id="echo_ws",
|
||||
artifact_id="echo",
|
||||
version=1,
|
||||
title="Echo",
|
||||
outcomes=("completed",),
|
||||
)
|
||||
|
||||
assert result["required_logical_sources"] == ["demo.personal"]
|
||||
assert result["suggested_bindings"] == {"demo.personal": "demo.personal"}
|
||||
|
||||
|
||||
async def test_create_artifact_from_workspace_returns_saved_false_when_invalid(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
@@ -1290,6 +1290,55 @@ def test_wf_draft_add_step_from_capability_uses_rpc_target(
|
||||
assert payload["status"] == "valid"
|
||||
|
||||
|
||||
def test_wf_draft_add_step_reports_bare_output_target_without_traceback(
|
||||
monkeypatch, tmp_path
|
||||
) -> None:
|
||||
server = build_local_static_workflow_server(tmp_path / "store")
|
||||
_patch_rpc_client_to_server(monkeypatch, server)
|
||||
config_path = tmp_path / "wf.json"
|
||||
config_path.write_text('{"version": 1}', encoding="utf-8")
|
||||
runner = CliRunner()
|
||||
base_args = ["--config", str(config_path), "--url", "http://test/rpc"]
|
||||
|
||||
created = runner.invoke(
|
||||
app,
|
||||
[
|
||||
*base_args,
|
||||
"draft",
|
||||
"create",
|
||||
"add_step_ws",
|
||||
"--capability",
|
||||
"wf.std.constant",
|
||||
"--name",
|
||||
"add_step",
|
||||
],
|
||||
)
|
||||
assert created.exit_code == 0, created.output
|
||||
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
*base_args,
|
||||
"draft",
|
||||
"add-step",
|
||||
"add_step_ws",
|
||||
"--revision",
|
||||
"1",
|
||||
"--step",
|
||||
"second",
|
||||
"--capability",
|
||||
"wf.std.constant",
|
||||
"--bind-output",
|
||||
"value=value",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "Traceback" not in result.output
|
||||
assert "--bind-output" in result.output
|
||||
assert "state.value" in result.output
|
||||
|
||||
|
||||
def test_wf_draft_compile_prints_compiled_plan(monkeypatch, tmp_path) -> None:
|
||||
server = build_local_static_workflow_server(tmp_path / "store")
|
||||
_patch_rpc_client_to_server(monkeypatch, server)
|
||||
|
||||
@@ -96,6 +96,10 @@ def test_schema_verbose_root_is_valid_json_schema() -> None:
|
||||
assert payload["title"] == "RawWorkflowPlan"
|
||||
|
||||
|
||||
def test_schema_full_alias_matches_verbose() -> None:
|
||||
assert _json_result("raw", "--full") == _json_result("raw", "--verbose")
|
||||
|
||||
|
||||
def test_schema_verbose_component_is_self_contained() -> None:
|
||||
payload = _json_result("NodeUse", "--verbose")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user