11 deleted/transferred tests because wf_api has some
This commit is contained in:
@@ -442,6 +442,26 @@ Do not move `wf_mcp.shared.pagination` in this slice. It is still used by proxy
|
||||
tool search/listing code, so treating it as dead workflow-surface debt would be
|
||||
incorrect.
|
||||
|
||||
### Post-5 Helper Cleanup: Workflow Surface Test Thinning
|
||||
|
||||
Concrete plan:
|
||||
|
||||
```text
|
||||
docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning.md
|
||||
```
|
||||
|
||||
Intent:
|
||||
|
||||
```text
|
||||
wf_api tests = canonical application behavior tests
|
||||
wf_mcp.workflow_surface tests = adapter/schema/live-source/integration smoke tests
|
||||
```
|
||||
|
||||
Do not replace stronger workflow-surface integration tests with weaker unit
|
||||
tests. Only remove a handler test when an equal-or-stronger `wf_api` test exists
|
||||
and at least one handler-level smoke/delegation test still protects the adapter
|
||||
path.
|
||||
|
||||
### Candidate Moves
|
||||
|
||||
```text
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
# wf_mcp Workflow Surface Test Thinning Ledger
|
||||
|
||||
This ledger records every `wf_mcp.workflow_surface` test removed or kept during
|
||||
the thinning pass. Do not delete a test unless the `replacement` column points
|
||||
to equal-or-stronger coverage.
|
||||
|
||||
| Test | Decision | Replacement / Reason |
|
||||
| --- | --- | --- |
|
||||
| test_artifacts::test_workflow_surface_lists_artifact_catalog_entries | keep | Handler adapter smoke test for compact artifact listing; reduced to minimal assertions. |
|
||||
| test_artifacts::test_workflow_surface_pages_and_filters_artifact_catalog_entries | remove | Covered by WorkflowArtifactApi list tests plus wf_api.listing pagination tests; handler keeps one list_artifacts smoke test. |
|
||||
| test_capabilities::test_workflow_surface_lists_planner_visible_capabilities | keep | Handler list smoke test; reduced to minimal adapter-path assertions. |
|
||||
| test_capabilities::test_workflow_surface_filters_stdlib_capabilities_by_source | remove | Covered by WorkflowCapabilityApi source/query filtering; handler list smoke remains. |
|
||||
| test_capabilities::test_workflow_surface_call_capability_returns_structured_error | keep | Protected: MCP/service behavior — structured error on capability call failure. |
|
||||
| test_capabilities::test_workflow_surface_lists_saved_wrapper_capabilities | remove | Covered by WorkflowCapabilityApi saved wrapper list tests. |
|
||||
| test_capabilities::test_workflow_surface_inspects_one_capability | keep | Handler inspect smoke test; ensures adapter path works for single capability inspection. |
|
||||
| test_capabilities::test_workflow_surface_inspect_capability_includes_wrapper_hints | keep | Protected: MCP/service behavior — wrapper hints detail through handler. |
|
||||
| test_capabilities::test_workflow_surface_inspects_saved_wrapper_capability | remove | Covered by WorkflowCapabilityApi saved wrapper inspect tests. |
|
||||
| test_capabilities::test_workflow_surface_does_not_auto_map_raw_mcp_content_blocks | keep | Protected: MCP content-block mapping behavior. |
|
||||
| test_deployments::test_workflow_surface_validates_deployment_dependencies | keep | Handler-level dependency validation has stronger next-action assertions than wf_api. |
|
||||
| test_deployments::test_workflow_surface_validate_deployment_live_check_is_opt_in | keep | Protected: MCP service adapter — live check is opt-in. |
|
||||
| test_deployments::test_workflow_surface_validate_deployment_live_check_reports_unreachable_source | keep | Protected: MCP service adapter — unreachable source reporting. |
|
||||
| test_deployments::test_workflow_surface_validate_deployment_live_check_reports_missing_connection | keep | Protected: MCP service adapter — missing connection reporting. |
|
||||
| test_deployments::test_workflow_surface_records_artifact_and_deployment_save_events | keep | Protected: service event recording. |
|
||||
| test_deployments::test_workflow_surface_save_deployment_accepts_deployment_id_alias | keep | Protected: request alias normalization. |
|
||||
| test_deployments::test_workflow_surface_deletes_deployment | keep | Protected: delete event recording. |
|
||||
| test_deployments::test_workflow_surface_save_deployment_rejects_id_and_deployment_id | keep | Protected: XOR validation on id/deployment_id. |
|
||||
| test_deployments::test_workflow_surface_lists_compact_deployment_summaries_and_inspects_detail | keep | Protected: compact-vs-detail response shape. |
|
||||
| test_drafts::test_workflow_surface_validates_draft_without_saving | remove | Covered by WorkflowDraftApi.validate_draft via test_delegation_smoke_validate_draft_equivalence. |
|
||||
| test_drafts::test_workflow_surface_rejects_unknown_draft_route_outcome_when_spec_is_known | keep | Live outcome lookup through handler; not duplicated in wf_api. |
|
||||
| test_drafts::test_workflow_surface_creates_artifact_from_draft_with_binding_suggestions | keep | Binding suggestions and artifact persistence through handler. |
|
||||
| test_drafts::test_workflow_surface_draft_artifact_requires_std_self_binding | keep | binding_missing diagnostic through handler/deployment integration. |
|
||||
| test_drafts::test_workflow_surface_patches_draft_without_saving | remove | Covered by WorkflowDraftApi.patch_draft. |
|
||||
| test_drafts::test_workflow_surface_creates_and_gets_draft_workspace | remove | Covered by WorkflowDraftApi.create_draft_workspace and get_draft_workspace assertions in tests/wf_api/test_drafts_service.py. |
|
||||
| test_drafts::test_workflow_surface_lists_draft_workspaces | remove | Covered by tests/wf_api/test_drafts_service.py::test_list_draft_workspaces_returns_sorted_summaries_without_drafts. |
|
||||
| test_drafts::test_workflow_surface_deletes_draft_workspace | remove | Covered by tests/wf_api/test_drafts_service.py::test_delete_draft_workspace_is_idempotent. |
|
||||
| test_drafts::test_workflow_surface_patch_helpers_update_draft_workspace | remove | Covered by tests/wf_api/test_drafts_service.py::test_draft_workspace_patch_helpers_update_revision_and_bindings. |
|
||||
| test_drafts::test_workflow_surface_validates_draft_workspace_with_live_outcomes | keep | Live outcome lookup through handler/service stack. |
|
||||
| test_drafts::test_workflow_surface_patches_draft_workspace_by_revision | remove | Covered by WorkflowDraftApi.patch_draft_workspace. |
|
||||
| test_drafts::test_workflow_surface_creates_minimal_draft_workspace_with_error_route | keep | MCP request model parsing and error route generation. |
|
||||
| test_drafts::test_workflow_surface_minimal_draft_honors_explicit_error_message_source | keep | Explicit error_message_source handling. |
|
||||
| test_drafts::test_minimal_draft_request_accepts_structural_error_message_source | keep | MCP Pydantic model validation (CreateMinimalDraftWorkspaceRequest). |
|
||||
| test_drafts::test_workflow_surface_accepts_canonical_bindings_for_minimal_workspace | keep | Canonical InputPathBinding/OutputBinding through handler. |
|
||||
| test_drafts::test_workflow_surface_creates_draft_workspace_from_capability_hints | keep | Wrapper hints and next-actions through handler. |
|
||||
| test_drafts::test_workflow_surface_creates_artifact_from_workspace | keep | Artifact persistence with schema snapshots through handler. |
|
||||
| test_drafts::test_workflow_surface_workspace_artifact_infers_raw_concrete_dependency | keep | Source dependency inference through handler. |
|
||||
| test_drafts::test_workflow_surface_creates_wrapper_from_workspace | keep | Wrapper creation through handler. |
|
||||
| test_drafts::test_workflow_surface_low_confidence_draft_returns_patch_guidance | keep | Next-action guidance with patch examples through handler. |
|
||||
| test_runs::test_raw_workflow_plan_uses_core_step_and_edge_models | keep | DEVIATION: Plan said wf_api covers this, but test_raw_workflow_plan_extraction.py only tests imports (canonical, compat, identity). The surface test is the only one exercising actual RawWorkflowPlan step/edge model parsing. Kept to avoid coverage gap. |
|
||||
| test_runs::test_workflow_surface_runs_non_interrupting_deployment | keep | Persisted run records, trace slicing, response model, next-actions. |
|
||||
| test_runs::test_workflow_surface_failed_deployment_exposes_error_on_run_and_inspect | keep | Failed run error exposure and inspect_run. |
|
||||
| test_runs::test_workflow_surface_run_deployment_can_include_trace_detail | keep | Protected: MCP TraceRange, RunDeploymentResult model validation. |
|
||||
| test_runs::test_workflow_surface_run_deployment_can_read_empty_trace_range | keep | Protected: empty trace range behavior. |
|
||||
| test_runs::test_workflow_surface_runs_deployment_with_bound_node_spec_dependency | keep | Protected: logical source binding. |
|
||||
| test_runs::test_workflow_surface_runs_artifact_created_from_concrete_node_ref | keep | Protected: concrete node ref artifact creation and run. |
|
||||
| test_runs::test_workflow_surface_detects_drift_from_saved_node_spec_snapshot | keep | Protected: schema drift detection. |
|
||||
| test_runs::test_workflow_surface_runs_deployment_with_bound_reducer_dependency | keep | Protected: reducer dependency integration. |
|
||||
| test_wrappers::test_workflow_surface_creates_wrapper_artifact_from_plan | keep | Handler integration: wrapper artifact creation from plan. |
|
||||
| test_wrappers::test_workflow_surface_creates_artifact_with_logical_node_refs | keep | Handler integration: logical node ref resolution. |
|
||||
| test_wrappers::test_workflow_surface_calls_saved_wrapper_artifact | keep | Handler integration: saved wrapper direct call. |
|
||||
| test_wrappers::test_workflow_surface_calls_live_node_spec_with_self_describing_response | keep | Handler integration: live node spec call. |
|
||||
| test_wrappers::test_workflow_surface_calls_saved_wrapper_artifact_with_deployment_bindings | keep | Protected: saved wrapper call with deployment bindings. |
|
||||
| test_next_actions::test_next_actions_from_high_confidence_wrapper_hints_can_validate | keep | NextActions model unit test. |
|
||||
| test_next_actions::test_next_actions_from_low_confidence_wrapper_hints_can_patch | keep | NextActions model unit test. |
|
||||
| test_next_actions::test_next_actions_from_runnable_deployment_recommends_run | keep | NextActions model unit test. |
|
||||
| test_next_actions::test_next_actions_from_unrunnable_deployment_recommends_validation_retry | keep | NextActions model unit test. |
|
||||
| test_next_actions::test_next_actions_from_completed_run_has_no_required_next_tool | keep | NextActions model unit test. |
|
||||
| test_next_actions::test_next_actions_from_failed_run_recommends_bounded_trace | keep | NextActions model unit test. |
|
||||
| test_next_actions::test_next_actions_from_interrupted_run_recommends_resume | keep | NextActions model unit test. |
|
||||
| test_next_actions::test_workflow_surface_next_actions_shim_reexports_canonical_model | keep | Shim re-export verification. |
|
||||
@@ -0,0 +1,538 @@
|
||||
# wf_mcp Workflow Surface Test Thinning Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Reduce duplicated `wf_mcp.workflow_surface` behavior tests now covered by `wf_api` tests while preserving adapter, schema, live-source, run, wrapper, and end-to-end coverage.
|
||||
|
||||
**Architecture:** `wf_api` tests are the canonical behavior tests for application-service logic. `wf_mcp.workflow_surface` tests should prove that `WorkflowSurfaceHandlers` exposes that behavior through the MCP-owned adapter boundary and should keep any test that exercises MCP-specific request models, live checks, service event recording, tool schema, or realistic integration paths.
|
||||
|
||||
**Tech Stack:** Python 3.14, pytest, `wf_api`, `wf_mcp.workflow_surface`, `WorkflowSurfaceHandlers`, ruff, basedpyright.
|
||||
|
||||
---
|
||||
|
||||
## Coverage Policy
|
||||
|
||||
Use this rule for every deletion:
|
||||
|
||||
```text
|
||||
Only remove a wf_mcp.workflow_surface test when an equal-or-stronger wf_api test
|
||||
already covers the behavior and a smaller handler smoke/delegation test still
|
||||
proves the adapter path.
|
||||
```
|
||||
|
||||
Keep tests that cover:
|
||||
|
||||
- MCP request/response Pydantic models such as `TraceRange`, `RunDeploymentResult`, and `CreateMinimalDraftWorkspaceRequest`.
|
||||
- live source checks through `WfMcpService` adapters.
|
||||
- service event recording.
|
||||
- handler-to-service wiring.
|
||||
- saved wrapper calls with deployment bindings.
|
||||
- source binding, schema drift, reducer dependency, subgraph, or durable run integration.
|
||||
- next-action guidance generated through real handler operations.
|
||||
|
||||
Thin tests that only repeat:
|
||||
|
||||
- list filtering/pagination details already covered by `WorkflowCapabilityApi` or `WorkflowArtifactApi`.
|
||||
- basic inspect/list payload details already covered by `wf_api` domain tests.
|
||||
- basic draft workspace CRUD details already covered by `WorkflowDraftApi`.
|
||||
- basic artifact/deployment CRUD details already covered by `WorkflowArtifactApi` or `WorkflowDeploymentApi`.
|
||||
|
||||
## File Map
|
||||
|
||||
| File | Planned role |
|
||||
| --- | --- |
|
||||
| `tests/wf_mcp/workflow_surface/test_artifacts.py` | Thin to one handler adapter smoke test for compact artifact listing. |
|
||||
| `tests/wf_mcp/workflow_surface/test_capabilities.py` | Thin list/filter/inspect duplicates; keep wrapper-hints, MCP content-block, direct call error, and saved wrapper coverage if not stronger in `wf_api`. |
|
||||
| `tests/wf_mcp/workflow_surface/test_deployments.py` | Keep live-check, events, alias/XOR, delete event, and compact-vs-detail tests. Maybe remove only duplicate dependency validation if covered by `wf_api`. |
|
||||
| `tests/wf_mcp/workflow_surface/test_drafts.py` | Keep most tests; remove only the simplest validate/patch/list CRUD duplicates if `wf_api` has equal coverage. |
|
||||
| `tests/wf_mcp/workflow_surface/test_runs.py` | Keep run/trace/model tests. Do not thin in first pass except the raw plan model extraction test if duplicated in `tests/wf_api/test_raw_workflow_plan_extraction.py`. |
|
||||
| `tests/wf_mcp/workflow_surface/test_wrappers.py` | Keep all tests in first pass; these are handler integration paths and direct capability REPL behavior. |
|
||||
| `tests/wf_api/*` | Do not weaken. Add missing behavior tests here before removing handler duplicates. |
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Build A Deletion Ledger Before Editing
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md`
|
||||
|
||||
- [ ] **Step 1: Create the ledger file**
|
||||
|
||||
Create `docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md`:
|
||||
|
||||
```markdown
|
||||
# wf_mcp Workflow Surface Test Thinning Ledger
|
||||
|
||||
This ledger records every `wf_mcp.workflow_surface` test removed or kept during
|
||||
the thinning pass. Do not delete a test unless the `replacement` column points
|
||||
to equal-or-stronger coverage.
|
||||
|
||||
| Test | Decision | Replacement / Reason |
|
||||
| --- | --- | --- |
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Populate the initial ledger with all workflow-surface tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "^def test_" tests/wf_mcp/workflow_surface
|
||||
```
|
||||
|
||||
Append each test name to the ledger with `Decision` set to `unclassified`.
|
||||
|
||||
Expected: the ledger contains every test from:
|
||||
|
||||
```text
|
||||
tests/wf_mcp/workflow_surface/test_artifacts.py
|
||||
tests/wf_mcp/workflow_surface/test_capabilities.py
|
||||
tests/wf_mcp/workflow_surface/test_deployments.py
|
||||
tests/wf_mcp/workflow_surface/test_drafts.py
|
||||
tests/wf_mcp/workflow_surface/test_runs.py
|
||||
tests/wf_mcp/workflow_surface/test_wrappers.py
|
||||
tests/wf_mcp/workflow_surface/test_next_actions.py
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Mark protected tests**
|
||||
|
||||
Mark these as `keep` unless a later task explicitly adds stronger coverage:
|
||||
|
||||
```text
|
||||
tests/wf_mcp/workflow_surface/test_capabilities.py::test_workflow_surface_call_capability_returns_structured_error
|
||||
tests/wf_mcp/workflow_surface/test_capabilities.py::test_workflow_surface_inspect_capability_includes_wrapper_hints
|
||||
tests/wf_mcp/workflow_surface/test_capabilities.py::test_workflow_surface_does_not_auto_map_raw_mcp_content_blocks
|
||||
tests/wf_mcp/workflow_surface/test_deployments.py::test_workflow_surface_validate_deployment_live_check_is_opt_in
|
||||
tests/wf_mcp/workflow_surface/test_deployments.py::test_workflow_surface_validate_deployment_live_check_reports_unreachable_source
|
||||
tests/wf_mcp/workflow_surface/test_deployments.py::test_workflow_surface_validate_deployment_live_check_reports_missing_connection
|
||||
tests/wf_mcp/workflow_surface/test_deployments.py::test_workflow_surface_records_artifact_and_deployment_save_events
|
||||
tests/wf_mcp/workflow_surface/test_deployments.py::test_workflow_surface_save_deployment_accepts_deployment_id_alias
|
||||
tests/wf_mcp/workflow_surface/test_deployments.py::test_workflow_surface_save_deployment_rejects_id_and_deployment_id
|
||||
tests/wf_mcp/workflow_surface/test_runs.py::test_workflow_surface_run_deployment_can_include_trace_detail
|
||||
tests/wf_mcp/workflow_surface/test_runs.py::test_workflow_surface_run_deployment_can_read_empty_trace_range
|
||||
tests/wf_mcp/workflow_surface/test_runs.py::test_workflow_surface_runs_deployment_with_bound_node_spec_dependency
|
||||
tests/wf_mcp/workflow_surface/test_runs.py::test_workflow_surface_runs_artifact_created_from_concrete_node_ref
|
||||
tests/wf_mcp/workflow_surface/test_runs.py::test_workflow_surface_detects_drift_from_saved_node_spec_snapshot
|
||||
tests/wf_mcp/workflow_surface/test_runs.py::test_workflow_surface_runs_deployment_with_bound_reducer_dependency
|
||||
tests/wf_mcp/workflow_surface/test_wrappers.py
|
||||
```
|
||||
|
||||
Reason: these cover adapter wiring, MCP/service behavior, direct wrapper calls,
|
||||
trace schema behavior, binding logic, reducer dependencies, or important
|
||||
integration seams.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Thin Artifact Listing Duplicates
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/wf_mcp/workflow_surface/test_artifacts.py`
|
||||
- Modify: `docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md`
|
||||
- Test: `tests/wf_api/test_artifact_api.py`, `tests/wf_mcp/workflow_surface/test_artifacts.py`
|
||||
|
||||
- [ ] **Step 1: Confirm wf_api artifact coverage**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "list_artifacts|pagination|kind|query|plan\" not in" tests/wf_api/test_artifact_api.py tests/wf_api/test_listing.py
|
||||
```
|
||||
|
||||
Expected: `wf_api` coverage exists for empty list payload, compact artifact rows,
|
||||
query/kind behavior, and pagination helper shape. If it does not, add the
|
||||
missing assertion to `tests/wf_api/test_artifact_api.py` before deleting any
|
||||
handler test.
|
||||
|
||||
- [ ] **Step 2: Keep one handler smoke test**
|
||||
|
||||
Keep `test_workflow_surface_lists_artifact_catalog_entries` and reduce it only
|
||||
if needed to these adapter-boundary assertions:
|
||||
|
||||
```python
|
||||
def test_workflow_surface_lists_artifact_catalog_entries() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "surface_artifacts")
|
||||
artifact_store.save_artifact(artifact())
|
||||
h = handlers(artifact_store)
|
||||
|
||||
payload = asyncio.run(h.list_artifacts())
|
||||
|
||||
nodes = payload["nodes"]
|
||||
assert payload["total"] == 1
|
||||
assert payload["next_cursor"] is None
|
||||
assert nodes[0]["name"] == "workflow.summarize_docs.v1"
|
||||
assert nodes[0]["artifact_id"] == "summarize_docs"
|
||||
assert "plan" not in nodes[0]
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Remove duplicate pagination/filter handler test**
|
||||
|
||||
Delete:
|
||||
|
||||
```text
|
||||
test_workflow_surface_pages_and_filters_artifact_catalog_entries
|
||||
```
|
||||
|
||||
Mark it in the ledger:
|
||||
|
||||
```text
|
||||
remove | Covered by WorkflowArtifactApi list tests plus wf_api.listing pagination tests; handler keeps one list_artifacts smoke test.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run artifact tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
uv run pytest tests/wf_api/test_artifact_api.py tests/wf_api/test_listing.py tests/wf_mcp/workflow_surface/test_artifacts.py -q
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Thin Capability List/Inspect Duplicates Conservatively
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/wf_mcp/workflow_surface/test_capabilities.py`
|
||||
- Modify: `docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md`
|
||||
- Test: `tests/wf_api/test_capability_api.py`, `tests/wf_mcp/workflow_surface/test_capabilities.py`
|
||||
|
||||
- [ ] **Step 1: Confirm wf_api capability coverage**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "list_capabilities|inspect_capability|saved_wrapper|wrapper_hints|runtime_error|content blocks" tests/wf_api/test_capability_api.py tests/wf_mcp/workflow_surface/test_capabilities.py
|
||||
```
|
||||
|
||||
Expected: `wf_api` covers planner-visible listing, source filtering, unknown
|
||||
inspect, saved wrapper list/inspect, and direct handler delegation smoke.
|
||||
|
||||
- [ ] **Step 2: Keep one handler list smoke test**
|
||||
|
||||
Keep `test_workflow_surface_lists_planner_visible_capabilities`, but keep it
|
||||
compact. It should assert adapter path and summary shape only:
|
||||
|
||||
```python
|
||||
def test_workflow_surface_lists_planner_visible_capabilities() -> None:
|
||||
h = handlers(FileWorkflowArtifactStore(local_temp_root() / "surface_caps"))
|
||||
|
||||
payload = asyncio.run(h.list_capabilities(limit=2))
|
||||
first = payload["capabilities"][0]
|
||||
|
||||
assert len(payload["capabilities"]) == 2
|
||||
assert payload["total"] >= 2
|
||||
assert payload["next_cursor"] == "2"
|
||||
assert first["kind"] == "node_spec"
|
||||
assert "input_schema" not in first
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Remove duplicate source-filter test if wf_api has it**
|
||||
|
||||
Delete:
|
||||
|
||||
```text
|
||||
test_workflow_surface_filters_stdlib_capabilities_by_source
|
||||
```
|
||||
|
||||
Only delete it if `tests/wf_api/test_capability_api.py` contains:
|
||||
|
||||
```text
|
||||
test_list_capabilities_filters_by_source
|
||||
```
|
||||
|
||||
Mark the ledger:
|
||||
|
||||
```text
|
||||
remove | Covered by WorkflowCapabilityApi source/query filtering; handler list smoke remains.
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Remove duplicate saved-wrapper list/inspect tests if wf_api has them**
|
||||
|
||||
Delete these only if `tests/wf_api/test_capability_api.py` has equivalent saved
|
||||
wrapper list and inspect tests:
|
||||
|
||||
```text
|
||||
test_workflow_surface_lists_saved_wrapper_capabilities
|
||||
test_workflow_surface_inspects_saved_wrapper_capability
|
||||
```
|
||||
|
||||
Mark the ledger:
|
||||
|
||||
```text
|
||||
remove | Covered by WorkflowCapabilityApi saved wrapper list/inspect tests.
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Keep MCP-specific and guidance-sensitive capability tests**
|
||||
|
||||
Keep these tests unchanged:
|
||||
|
||||
```text
|
||||
test_workflow_surface_call_capability_returns_structured_error
|
||||
test_workflow_surface_inspect_capability_includes_wrapper_hints
|
||||
test_workflow_surface_does_not_auto_map_raw_mcp_content_blocks
|
||||
```
|
||||
|
||||
Do not remove `test_workflow_surface_inspects_one_capability` unless there is
|
||||
still another handler-level inspect smoke test after this task.
|
||||
|
||||
- [ ] **Step 6: Run capability tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
uv run pytest tests/wf_api/test_capability_api.py tests/wf_mcp/workflow_surface/test_capabilities.py -q
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Thin Deployment Tests Only Where Purely Duplicated
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/wf_mcp/workflow_surface/test_deployments.py`
|
||||
- Modify: `docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md`
|
||||
- Test: `tests/wf_api/test_deployment_api.py`, `tests/wf_mcp/workflow_surface/test_deployments.py`
|
||||
|
||||
- [ ] **Step 1: Keep MCP live-check and event tests**
|
||||
|
||||
Do not delete:
|
||||
|
||||
```text
|
||||
test_workflow_surface_validate_deployment_live_check_is_opt_in
|
||||
test_workflow_surface_validate_deployment_live_check_reports_unreachable_source
|
||||
test_workflow_surface_validate_deployment_live_check_reports_missing_connection
|
||||
test_workflow_surface_records_artifact_and_deployment_save_events
|
||||
test_workflow_surface_save_deployment_accepts_deployment_id_alias
|
||||
test_workflow_surface_deletes_deployment
|
||||
test_workflow_surface_save_deployment_rejects_id_and_deployment_id
|
||||
test_workflow_surface_lists_compact_deployment_summaries_and_inspects_detail
|
||||
```
|
||||
|
||||
Reason: these exercise MCP service adapters, event recording, request alias
|
||||
normalization, and compact-vs-detail response shape.
|
||||
|
||||
- [ ] **Step 2: Evaluate basic dependency validation duplicate**
|
||||
|
||||
Check whether `tests/wf_api/test_deployment_api.py` covers dependency validation
|
||||
next-actions for an unrunnable deployment:
|
||||
|
||||
```bash
|
||||
rg -n "source_missing|binding_missing|next_actions|unrunnable" tests/wf_api/test_deployment_api.py
|
||||
```
|
||||
|
||||
If the `wf_api` test has equal next-action assertions, delete:
|
||||
|
||||
```text
|
||||
test_workflow_surface_validates_deployment_dependencies
|
||||
```
|
||||
|
||||
If it does not, keep the handler test and mark it:
|
||||
|
||||
```text
|
||||
keep | Handler-level dependency validation still has stronger next-action assertions than wf_api.
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run deployment tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
uv run pytest tests/wf_api/test_deployment_api.py tests/wf_mcp/workflow_surface/test_deployments.py -q
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Thin Draft Tests With High Caution
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/wf_mcp/workflow_surface/test_drafts.py`
|
||||
- Modify: `docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md`
|
||||
- Test: `tests/wf_api/test_drafts_service.py`, `tests/wf_mcp/workflow_surface/test_drafts.py`
|
||||
|
||||
- [ ] **Step 1: Identify pure draft API duplicates**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "validate_draft|patch_draft|list_draft_workspaces|delete_draft_workspace|create_minimal_draft_workspace|create_draft_workspace_from_capability|create_artifact_from_workspace|create_wrapper_from_workspace" tests/wf_api/test_drafts_service.py tests/wf_api/test_artifact_api.py tests/wf_api/test_capability_api.py tests/wf_mcp/workflow_surface/test_drafts.py
|
||||
```
|
||||
|
||||
Expected: most simple draft workspace CRUD and patch helper behavior exists in
|
||||
`tests/wf_api/test_drafts_service.py`.
|
||||
|
||||
- [ ] **Step 2: Remove only pure CRUD duplicates**
|
||||
|
||||
Candidates for removal if `wf_api` tests cover equal behavior:
|
||||
|
||||
```text
|
||||
test_workflow_surface_validates_draft_without_saving
|
||||
test_workflow_surface_patches_draft_without_saving
|
||||
test_workflow_surface_creates_and_gets_draft_workspace
|
||||
test_workflow_surface_lists_draft_workspaces
|
||||
test_workflow_surface_deletes_draft_workspace
|
||||
test_workflow_surface_patch_helpers_update_draft_workspace
|
||||
test_workflow_surface_patches_draft_workspace_by_revision
|
||||
```
|
||||
|
||||
For each removed test, add a ledger row with the exact `wf_api` replacement test.
|
||||
|
||||
- [ ] **Step 3: Keep guidance, model, and artifact integration tests**
|
||||
|
||||
Do not delete these in this pass:
|
||||
|
||||
```text
|
||||
test_workflow_surface_rejects_unknown_draft_route_outcome_when_spec_is_known
|
||||
test_workflow_surface_creates_artifact_from_draft_with_binding_suggestions
|
||||
test_workflow_surface_draft_artifact_requires_std_self_binding
|
||||
test_workflow_surface_validates_draft_workspace_with_live_outcomes
|
||||
test_workflow_surface_creates_minimal_draft_workspace_with_error_route
|
||||
test_workflow_surface_minimal_draft_honors_explicit_error_message_source
|
||||
test_minimal_draft_request_accepts_structural_error_message_source
|
||||
test_workflow_surface_accepts_canonical_bindings_for_minimal_workspace
|
||||
test_workflow_surface_creates_draft_workspace_from_capability_hints
|
||||
test_workflow_surface_creates_artifact_from_workspace
|
||||
test_workflow_surface_workspace_artifact_infers_raw_concrete_dependency
|
||||
test_workflow_surface_creates_wrapper_from_workspace
|
||||
test_workflow_surface_low_confidence_draft_returns_patch_guidance
|
||||
```
|
||||
|
||||
Reason: these cover live outcome lookup, request model parsing, wrapper hints,
|
||||
next actions, artifact persistence, and source dependency inference.
|
||||
|
||||
- [ ] **Step 4: Run draft tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
uv run pytest tests/wf_api/test_drafts_service.py tests/wf_api/test_artifact_api.py tests/wf_api/test_capability_api.py tests/wf_mcp/workflow_surface/test_drafts.py -q
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Keep Run And Wrapper Tests Mostly Intact
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/wf_mcp/workflow_surface/test_runs.py`
|
||||
- Modify: `docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md`
|
||||
- Test: `tests/wf_api/test_run_api.py`, `tests/wf_mcp/workflow_surface/test_runs.py`, `tests/wf_mcp/workflow_surface/test_wrappers.py`
|
||||
|
||||
- [ ] **Step 1: Remove raw plan model duplicate only**
|
||||
|
||||
If `tests/wf_api/test_raw_workflow_plan_extraction.py` covers core step and edge
|
||||
model parsing, delete:
|
||||
|
||||
```text
|
||||
tests/wf_mcp/workflow_surface/test_runs.py::test_raw_workflow_plan_uses_core_step_and_edge_models
|
||||
```
|
||||
|
||||
Mark the ledger:
|
||||
|
||||
```text
|
||||
remove | RawWorkflowPlan extraction is canonical in wf_api model tests.
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Keep run deployment behavior tests**
|
||||
|
||||
Keep all remaining tests in `tests/wf_mcp/workflow_surface/test_runs.py`.
|
||||
|
||||
Reason: they cover persisted run records, trace slicing with MCP `TraceRange`,
|
||||
response Pydantic model validation, logical source binding, schema drift, and
|
||||
reducer dependency integration through the handler/service stack.
|
||||
|
||||
- [ ] **Step 3: Keep wrapper tests**
|
||||
|
||||
Do not delete tests in `tests/wf_mcp/workflow_surface/test_wrappers.py` in this
|
||||
pass.
|
||||
|
||||
Reason: wrapper direct calls and deployment-bound wrapper calls are meaningful
|
||||
handler integration tests even if `WorkflowCapabilityApi` also has lower-level
|
||||
coverage.
|
||||
|
||||
- [ ] **Step 4: Run run/wrapper tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
uv run pytest tests/wf_api/test_run_api.py tests/wf_api/test_raw_workflow_plan_extraction.py tests/wf_mcp/workflow_surface/test_runs.py tests/wf_mcp/workflow_surface/test_wrappers.py -q
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
---
|
||||
|
||||
## Task 7: Final Review And Verification
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md`
|
||||
- Test: all workflow-surface and wf_api tests
|
||||
|
||||
- [ ] **Step 1: Ensure ledger has no unclassified rows**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "unclassified" docs/superpowers/plans/2026-06-02-wf-mcp-workflow-surface-test-thinning-ledger.md
|
||||
```
|
||||
|
||||
Expected: no matches.
|
||||
|
||||
- [ ] **Step 2: Run full focused API and adapter tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
uv run pytest tests/wf_api tests/wf_mcp/workflow_surface tests/wf_mcp/server/test_tools.py tests/wf_mcp/server/test_config.py -q
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 3: Run lint and type checks**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
uv run ruff check tests/wf_api tests/wf_mcp/workflow_surface
|
||||
uv run ruff format --check tests/wf_api tests/wf_mcp/workflow_surface
|
||||
uv run basedpyright --level error
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
- Ruff commands pass.
|
||||
- Basedpyright reports `0 errors`; if it exits nonzero only because workspace
|
||||
enumeration exceeds 10 seconds, report that exact output as an environment
|
||||
issue rather than a type failure.
|
||||
|
||||
- [ ] **Step 4: Optional full suite**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
uv run pytest -q
|
||||
```
|
||||
|
||||
Expected: existing suite status remains at least as good as before this pass.
|
||||
|
||||
---
|
||||
|
||||
## Handoff Report Requirements
|
||||
|
||||
When done, report:
|
||||
|
||||
- Tests removed, grouped by file.
|
||||
- Tests kept intentionally, with reasons for any controversial keeps.
|
||||
- Any new or strengthened `wf_api` tests.
|
||||
- Ledger path.
|
||||
- Verification commands and exact outputs.
|
||||
- Deviations from the plan.
|
||||
|
||||
## Self-Review
|
||||
|
||||
- Spec coverage: the plan preserves “good tests” by requiring a ledger and exact replacement coverage before any deletion.
|
||||
- Placeholder scan: no deferred implementation slots; each deletion candidate has a guard and replacement rule.
|
||||
- Type consistency: all referenced test paths and test names were taken from the current tree inspection.
|
||||
@@ -157,6 +157,11 @@ def test_list_capabilities_includes_saved_wrapper() -> None:
|
||||
assert row["kind"] == "wrapper_artifact"
|
||||
assert row["artifact_id"] == "echo_wrapper"
|
||||
assert row["version"] == 1
|
||||
assert row["title"] == "Echo"
|
||||
assert row["description"] == "Reusable echo wrapper."
|
||||
assert row["outcomes"] == ["completed"]
|
||||
assert row["input_fields"] == ["text"]
|
||||
assert row["output_fields"] == ["echoed"]
|
||||
|
||||
|
||||
def test_inspect_capability_saved_wrapper() -> None:
|
||||
@@ -177,8 +182,15 @@ def test_inspect_capability_saved_wrapper() -> None:
|
||||
assert detail["kind"] == "wrapper_artifact"
|
||||
assert detail["artifact_id"] == "echo_wrapper"
|
||||
assert detail["outcomes"] == ["completed"]
|
||||
assert "input_schema" in detail
|
||||
assert detail["input_schema"]["properties"]["text"]["type"] == "string"
|
||||
assert detail["output_schema"]["properties"]["echoed"]["type"] == "string"
|
||||
hints = detail["wrapper_hints"]
|
||||
assert hints["capability_name"] == "workflow.echo_wrapper.v1"
|
||||
assert hints["declared_outcomes"] == ["completed"]
|
||||
assert hints["suggested_wrapper_outcomes"] == ["completed"]
|
||||
assert hints["input_map"] == {"input.text": "text"}
|
||||
assert hints["output_map"] == {"echoed": "state.echoed"}
|
||||
|
||||
|
||||
def test_call_capability_saved_wrapper() -> None:
|
||||
|
||||
@@ -108,6 +108,67 @@ def test_create_draft_workspace_creates_workspace() -> None:
|
||||
|
||||
assert result["workspace_id"] == "echo_ws"
|
||||
assert result["revision"] == 1
|
||||
fetched = asyncio.run(
|
||||
api.get_draft_workspace(workspace_id="echo_ws", include_draft=True)
|
||||
)
|
||||
assert fetched["workspace_id"] == "echo_ws"
|
||||
assert fetched["title"] == "Echo Workspace"
|
||||
assert fetched["draft"]["steps"]["echo"]["use"] == "demo.personal.echo_tool"
|
||||
|
||||
|
||||
def test_list_draft_workspaces_returns_sorted_summaries_without_drafts() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "drafts_list_workspaces"
|
||||
)
|
||||
api, _service = _draft_api(artifact_store)
|
||||
asyncio.run(
|
||||
api.create_draft_workspace(
|
||||
workspace_id="b_draft",
|
||||
title="B Draft",
|
||||
draft=_echo_draft(),
|
||||
)
|
||||
)
|
||||
asyncio.run(
|
||||
api.create_draft_workspace(
|
||||
workspace_id="a_draft",
|
||||
title="A Draft",
|
||||
draft=_echo_draft(),
|
||||
)
|
||||
)
|
||||
|
||||
result = asyncio.run(api.list_draft_workspaces())
|
||||
|
||||
assert [workspace["workspace_id"] for workspace in result["workspaces"]] == [
|
||||
"a_draft",
|
||||
"b_draft",
|
||||
]
|
||||
assert result["workspaces"][0]["title"] == "A Draft"
|
||||
assert "draft" not in result["workspaces"][0]
|
||||
|
||||
|
||||
def test_delete_draft_workspace_is_idempotent() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "drafts_delete_workspace"
|
||||
)
|
||||
api, _service = _draft_api(artifact_store)
|
||||
asyncio.run(
|
||||
api.create_draft_workspace(
|
||||
workspace_id="echo_ws",
|
||||
draft=_echo_draft(),
|
||||
)
|
||||
)
|
||||
|
||||
deleted = asyncio.run(api.delete_draft_workspace(workspace_id="echo_ws"))
|
||||
deleted_again = asyncio.run(api.delete_draft_workspace(workspace_id="echo_ws"))
|
||||
listed = asyncio.run(api.list_draft_workspaces())
|
||||
|
||||
assert deleted["workspace_id"] == "echo_ws"
|
||||
assert deleted["deleted"] is True
|
||||
assert deleted["status"] == "deleted"
|
||||
assert deleted_again["workspace_id"] == "echo_ws"
|
||||
assert deleted_again["deleted"] is False
|
||||
assert deleted_again["status"] == "not_found"
|
||||
assert listed["workspaces"] == []
|
||||
|
||||
|
||||
def test_patch_draft_workspace_updates_revision() -> None:
|
||||
@@ -134,6 +195,74 @@ def test_patch_draft_workspace_updates_revision() -> None:
|
||||
assert patched["status"] == "valid"
|
||||
|
||||
|
||||
def test_draft_workspace_patch_helpers_update_revision_and_bindings() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "drafts_patch_helpers"
|
||||
)
|
||||
api, _service = _draft_api(artifact_store)
|
||||
asyncio.run(
|
||||
api.create_draft_workspace(
|
||||
workspace_id="echo_ws",
|
||||
draft=_echo_draft(),
|
||||
)
|
||||
)
|
||||
|
||||
named = asyncio.run(
|
||||
api.set_draft_name(
|
||||
workspace_id="echo_ws",
|
||||
revision=1,
|
||||
name="echo_v2",
|
||||
)
|
||||
)
|
||||
routed = asyncio.run(
|
||||
api.set_draft_route(
|
||||
workspace_id="echo_ws",
|
||||
revision=2,
|
||||
step_id="echo",
|
||||
outcome="error",
|
||||
target="__end__",
|
||||
)
|
||||
)
|
||||
input_mapped = asyncio.run(
|
||||
api.set_step_input_map(
|
||||
workspace_id="echo_ws",
|
||||
revision=3,
|
||||
step_id="echo",
|
||||
input_map={"input.text": "message"},
|
||||
)
|
||||
)
|
||||
output_mapped = asyncio.run(
|
||||
api.set_step_output_map(
|
||||
workspace_id="echo_ws",
|
||||
revision=4,
|
||||
step_id="echo",
|
||||
output_map={"echoed": "state.echoed"},
|
||||
)
|
||||
)
|
||||
fetched = asyncio.run(
|
||||
api.get_draft_workspace(workspace_id="echo_ws", include_draft=True)
|
||||
)
|
||||
|
||||
assert named["revision"] == 2
|
||||
assert routed["revision"] == 3
|
||||
assert input_mapped["revision"] == 4
|
||||
assert output_mapped["revision"] == 5
|
||||
assert fetched["draft"]["name"] == "echo_v2"
|
||||
assert fetched["draft"]["routes"]["echo"]["error"] == "__end__"
|
||||
assert fetched["draft"]["steps"]["echo"]["input"] == [
|
||||
{
|
||||
"target": {"root": "local", "parts": ["message"]},
|
||||
"path": {"root": "input", "parts": ["text"]},
|
||||
}
|
||||
]
|
||||
assert fetched["draft"]["steps"]["echo"]["output"] == [
|
||||
{
|
||||
"source": {"root": "local", "parts": ["echoed"]},
|
||||
"target": {"root": "state", "parts": ["echoed"]},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_validate_draft_workspace_refreshes_status() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "drafts_validate_workspace"
|
||||
|
||||
@@ -16,56 +16,8 @@ def test_workflow_surface_lists_artifact_catalog_entries() -> None:
|
||||
payload = asyncio.run(h.list_artifacts())
|
||||
|
||||
nodes = payload["nodes"]
|
||||
assert len(nodes) == 1
|
||||
assert payload["total"] == 1
|
||||
assert payload["next_cursor"] is None
|
||||
assert nodes[0]["name"] == "workflow.summarize_docs.v1"
|
||||
assert nodes[0]["artifact_id"] == "summarize_docs"
|
||||
assert nodes[0]["version"] == 1
|
||||
assert nodes[0]["kind"] == "workflow"
|
||||
assert nodes[0]["required_sources"] == ["context7"]
|
||||
assert "plan" not in nodes[0]
|
||||
|
||||
|
||||
def test_workflow_surface_pages_and_filters_artifact_catalog_entries() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_artifact_pages"
|
||||
)
|
||||
artifact_store.save_artifact(artifact())
|
||||
artifact_store.save_artifact(
|
||||
artifact().model_copy(
|
||||
update={
|
||||
"id": "echo_wrapper",
|
||||
"version": 2,
|
||||
"kind": "wrapper",
|
||||
"title": "Echo Wrapper",
|
||||
"description": "Reusable echo wrapper.",
|
||||
}
|
||||
)
|
||||
)
|
||||
artifact_store.save_artifact(
|
||||
artifact().model_copy(
|
||||
update={
|
||||
"id": "browser_click",
|
||||
"title": "Browser Click",
|
||||
"description": "Open a page and wait for a click.",
|
||||
}
|
||||
)
|
||||
)
|
||||
h = handlers(artifact_store)
|
||||
|
||||
first_page = asyncio.run(h.list_artifacts(limit=2))
|
||||
second_page = asyncio.run(
|
||||
h.list_artifacts(cursor=first_page["next_cursor"], limit=2)
|
||||
)
|
||||
wrappers = asyncio.run(h.list_artifacts(kind="wrapper", query="echo"))
|
||||
|
||||
assert first_page["total"] == 3
|
||||
assert first_page["next_cursor"] == "2"
|
||||
assert len(first_page["nodes"]) == 2
|
||||
assert len(second_page["nodes"]) == 1
|
||||
assert second_page["next_cursor"] is None
|
||||
assert wrappers["total"] == 1
|
||||
assert wrappers["nodes"][0]["artifact_id"] == "echo_wrapper"
|
||||
assert wrappers["nodes"][0]["kind"] == "wrapper"
|
||||
assert "plan" not in wrappers["nodes"][0]
|
||||
|
||||
@@ -11,7 +11,6 @@ from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
|
||||
from ..test_support import echo_tool, local_temp_root
|
||||
from .conftest import (
|
||||
ContentOnlyOutputAdapter,
|
||||
echo_artifact,
|
||||
failing_tool,
|
||||
handlers,
|
||||
)
|
||||
@@ -21,30 +20,13 @@ def test_workflow_surface_lists_planner_visible_capabilities() -> None:
|
||||
h = handlers(FileWorkflowArtifactStore(local_temp_root() / "surface_caps"))
|
||||
|
||||
payload = asyncio.run(h.list_capabilities(limit=2))
|
||||
names = [capability["name"] for capability in payload["capabilities"]]
|
||||
first = payload["capabilities"][0]
|
||||
|
||||
assert len(names) == 2
|
||||
assert len(payload["capabilities"]) == 2
|
||||
assert payload["total"] >= 2
|
||||
assert payload["next_cursor"] == "2"
|
||||
assert "description" in first
|
||||
assert "source_id" in first
|
||||
assert first["kind"] == "node_spec"
|
||||
assert "input_fields" in first
|
||||
assert "output_fields" in first
|
||||
assert "input_schema" not in first
|
||||
assert "wf.admin.list_sources" not in names
|
||||
|
||||
|
||||
def test_workflow_surface_filters_stdlib_capabilities_by_source() -> None:
|
||||
h = handlers(FileWorkflowArtifactStore(local_temp_root() / "surface_filtered_caps"))
|
||||
|
||||
payload = asyncio.run(h.list_capabilities(source_id="wf.std", query="truthy"))
|
||||
|
||||
assert [capability["name"] for capability in payload["capabilities"]] == [
|
||||
"wf.std.truthy"
|
||||
]
|
||||
assert payload["capabilities"][0]["source_id"] == "wf.std"
|
||||
|
||||
|
||||
def test_workflow_surface_call_capability_returns_structured_error() -> None:
|
||||
@@ -78,36 +60,6 @@ def test_workflow_surface_call_capability_returns_structured_error() -> None:
|
||||
assert "upstream exploded" in payload["diagnostics"][0]["message"]
|
||||
|
||||
|
||||
def test_workflow_surface_lists_saved_wrapper_capabilities() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_wrapper_caps"
|
||||
)
|
||||
artifact_store.save_artifact(
|
||||
echo_artifact().model_copy(
|
||||
update={
|
||||
"id": "echo_wrapper",
|
||||
"kind": "wrapper",
|
||||
"description": "Reusable echo wrapper.",
|
||||
}
|
||||
)
|
||||
)
|
||||
artifact_store.save_artifact(echo_artifact())
|
||||
h = handlers(artifact_store)
|
||||
|
||||
payload = asyncio.run(h.list_capabilities(source_id="workflow", query="echo"))
|
||||
|
||||
names = [capability["name"] for capability in payload["capabilities"]]
|
||||
assert names == ["workflow.echo_wrapper.v1"]
|
||||
assert payload["capabilities"][0]["source_id"] == "workflow"
|
||||
assert payload["capabilities"][0]["kind"] == "wrapper_artifact"
|
||||
assert payload["capabilities"][0]["artifact_id"] == "echo_wrapper"
|
||||
assert payload["capabilities"][0]["version"] == 1
|
||||
assert payload["capabilities"][0]["title"] == "Echo"
|
||||
assert payload["capabilities"][0]["outcomes"] == ["completed"]
|
||||
assert payload["capabilities"][0]["input_fields"] == ["text"]
|
||||
assert payload["capabilities"][0]["output_fields"] == ["echoed"]
|
||||
|
||||
|
||||
def test_workflow_surface_inspects_one_capability() -> None:
|
||||
h = handlers(FileWorkflowArtifactStore(local_temp_root() / "surface_inspect_cap"))
|
||||
|
||||
@@ -143,33 +95,6 @@ def test_workflow_surface_inspect_capability_includes_wrapper_hints() -> None:
|
||||
assert hints["outcome_policy"] == "preserve_declared"
|
||||
|
||||
|
||||
def test_workflow_surface_inspects_saved_wrapper_capability() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_inspect_wrapper_cap"
|
||||
)
|
||||
artifact_store.save_artifact(
|
||||
echo_artifact().model_copy(update={"id": "echo_wrapper", "kind": "wrapper"})
|
||||
)
|
||||
h = handlers(artifact_store)
|
||||
|
||||
payload = asyncio.run(
|
||||
h.inspect_capability(qualified_name="workflow.echo_wrapper.v1")
|
||||
)
|
||||
|
||||
assert payload["name"] == "workflow.echo_wrapper.v1"
|
||||
assert payload["source_id"] == "workflow"
|
||||
assert payload["kind"] == "wrapper_artifact"
|
||||
assert payload["artifact_id"] == "echo_wrapper"
|
||||
assert payload["outcomes"] == ["completed"]
|
||||
assert "input_schema" in payload
|
||||
hints = payload["wrapper_hints"]
|
||||
assert hints["capability_name"] == "workflow.echo_wrapper.v1"
|
||||
assert hints["declared_outcomes"] == ["completed"]
|
||||
assert hints["suggested_wrapper_outcomes"] == ["completed"]
|
||||
assert hints["input_map"] == {"input.text": "text"}
|
||||
assert hints["output_map"] == {"echoed": "state.echoed"}
|
||||
|
||||
|
||||
def test_workflow_surface_does_not_auto_map_raw_mcp_content_blocks() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_content_only_content_hint"
|
||||
|
||||
@@ -20,20 +20,6 @@ from .conftest import (
|
||||
)
|
||||
|
||||
|
||||
def test_workflow_surface_validates_draft_without_saving() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_draft_validate"
|
||||
)
|
||||
h = handlers(artifact_store)
|
||||
|
||||
payload = asyncio.run(h.validate_draft(draft=echo_draft()))
|
||||
|
||||
assert payload["status"] == "valid"
|
||||
assert payload["diagnostics"] == []
|
||||
assert payload["compiled_plan"]["nodes"][0]["type"] == "node"
|
||||
assert not artifact_store.list_artifacts()
|
||||
|
||||
|
||||
def test_workflow_surface_rejects_unknown_draft_route_outcome_when_spec_is_known() -> (
|
||||
None
|
||||
):
|
||||
@@ -129,177 +115,6 @@ def test_workflow_surface_draft_artifact_requires_std_self_binding() -> None:
|
||||
assert payload["diagnostics"][0]["logical_ref"] == "wf.std.replace"
|
||||
|
||||
|
||||
def test_workflow_surface_patches_draft_without_saving() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_draft_patch"
|
||||
)
|
||||
h = handlers(artifact_store)
|
||||
|
||||
payload = asyncio.run(
|
||||
h.patch_draft(
|
||||
draft=echo_draft(),
|
||||
patch=[
|
||||
{
|
||||
"op": "replace",
|
||||
"path": "/steps/echo/input/0/target/parts/0",
|
||||
"value": "message",
|
||||
}
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
assert payload["status"] == "valid"
|
||||
assert payload["draft"]["steps"]["echo"]["input"][0]["target"] == {
|
||||
"root": "local",
|
||||
"parts": ["message"],
|
||||
}
|
||||
assert not artifact_store.list_artifacts()
|
||||
|
||||
|
||||
def test_workflow_surface_creates_and_gets_draft_workspace() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "surface_workspace")
|
||||
h = handlers(artifact_store)
|
||||
|
||||
created = asyncio.run(
|
||||
h.create_draft_workspace(
|
||||
workspace_id="echo_draft",
|
||||
title="Echo Draft",
|
||||
draft=echo_draft(),
|
||||
)
|
||||
)
|
||||
fetched = asyncio.run(
|
||||
h.get_draft_workspace(
|
||||
workspace_id="echo_draft",
|
||||
include_draft=True,
|
||||
)
|
||||
)
|
||||
|
||||
assert created["workspace_id"] == "echo_draft"
|
||||
assert created["revision"] == 1
|
||||
assert fetched["draft"]["steps"]["echo"]["use"] == "demo.personal.echo_tool"
|
||||
|
||||
|
||||
def test_workflow_surface_lists_draft_workspaces() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_list"
|
||||
)
|
||||
h = handlers(artifact_store)
|
||||
asyncio.run(
|
||||
h.create_draft_workspace(
|
||||
workspace_id="b_draft",
|
||||
draft=echo_draft(),
|
||||
title="B Draft",
|
||||
)
|
||||
)
|
||||
asyncio.run(
|
||||
h.create_draft_workspace(
|
||||
workspace_id="a_draft",
|
||||
draft=echo_draft(),
|
||||
title="A Draft",
|
||||
)
|
||||
)
|
||||
|
||||
payload = asyncio.run(h.list_draft_workspaces())
|
||||
|
||||
assert [workspace["workspace_id"] for workspace in payload["workspaces"]] == [
|
||||
"a_draft",
|
||||
"b_draft",
|
||||
]
|
||||
assert payload["workspaces"][0]["title"] == "A Draft"
|
||||
assert "draft" not in payload["workspaces"][0]
|
||||
|
||||
|
||||
def test_workflow_surface_deletes_draft_workspace() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_delete"
|
||||
)
|
||||
h = handlers(artifact_store)
|
||||
asyncio.run(
|
||||
h.create_draft_workspace(
|
||||
workspace_id="echo_draft",
|
||||
draft=echo_draft(),
|
||||
)
|
||||
)
|
||||
|
||||
deleted = asyncio.run(h.delete_draft_workspace(workspace_id="echo_draft"))
|
||||
deleted_again = asyncio.run(h.delete_draft_workspace(workspace_id="echo_draft"))
|
||||
listed = asyncio.run(h.list_draft_workspaces())
|
||||
|
||||
assert deleted["deleted"] is True
|
||||
assert deleted["status"] == "deleted"
|
||||
assert deleted_again["deleted"] is False
|
||||
assert deleted_again["status"] == "not_found"
|
||||
assert listed["workspaces"] == []
|
||||
|
||||
|
||||
def test_workflow_surface_patch_helpers_update_draft_workspace() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_patch_helpers"
|
||||
)
|
||||
h = handlers(artifact_store)
|
||||
asyncio.run(
|
||||
h.create_draft_workspace(
|
||||
workspace_id="echo_draft",
|
||||
draft=echo_draft(),
|
||||
)
|
||||
)
|
||||
|
||||
named = asyncio.run(
|
||||
h.set_draft_name(
|
||||
workspace_id="echo_draft",
|
||||
revision=1,
|
||||
name="echo_v2",
|
||||
)
|
||||
)
|
||||
routed = asyncio.run(
|
||||
h.set_draft_route(
|
||||
workspace_id="echo_draft",
|
||||
revision=2,
|
||||
step_id="echo",
|
||||
outcome="error",
|
||||
target="__end__",
|
||||
)
|
||||
)
|
||||
input_mapped = asyncio.run(
|
||||
h.set_step_input_map(
|
||||
workspace_id="echo_draft",
|
||||
revision=3,
|
||||
step_id="echo",
|
||||
input_map={"input.text": "message"},
|
||||
)
|
||||
)
|
||||
output_mapped = asyncio.run(
|
||||
h.set_step_output_map(
|
||||
workspace_id="echo_draft",
|
||||
revision=4,
|
||||
step_id="echo",
|
||||
output_map={"echoed": "state.echoed"},
|
||||
)
|
||||
)
|
||||
fetched = asyncio.run(
|
||||
h.get_draft_workspace(workspace_id="echo_draft", include_draft=True)
|
||||
)
|
||||
|
||||
assert named["revision"] == 2
|
||||
assert routed["revision"] == 3
|
||||
assert input_mapped["revision"] == 4
|
||||
assert output_mapped["revision"] == 5
|
||||
assert fetched["draft"]["name"] == "echo_v2"
|
||||
assert fetched["draft"]["routes"]["echo"]["error"] == "__end__"
|
||||
assert fetched["draft"]["steps"]["echo"]["input"] == [
|
||||
{
|
||||
"target": {"root": "local", "parts": ["message"]},
|
||||
"path": {"root": "input", "parts": ["text"]},
|
||||
}
|
||||
]
|
||||
assert fetched["draft"]["steps"]["echo"]["output"] == [
|
||||
{
|
||||
"source": {"root": "local", "parts": ["echoed"]},
|
||||
"target": {"root": "state", "parts": ["echoed"]},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_workflow_surface_validates_draft_workspace_with_live_outcomes() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_validate"
|
||||
@@ -331,30 +146,6 @@ def test_workflow_surface_validates_draft_workspace_with_live_outcomes() -> None
|
||||
assert fetched["status"] == "invalid"
|
||||
|
||||
|
||||
def test_workflow_surface_patches_draft_workspace_by_revision() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_patch"
|
||||
)
|
||||
h = handlers(artifact_store)
|
||||
asyncio.run(
|
||||
h.create_draft_workspace(
|
||||
workspace_id="echo_draft",
|
||||
draft=echo_draft(),
|
||||
)
|
||||
)
|
||||
|
||||
patched = asyncio.run(
|
||||
h.patch_draft_workspace(
|
||||
workspace_id="echo_draft",
|
||||
revision=1,
|
||||
patch=[{"op": "replace", "path": "/name", "value": "echo_v2"}],
|
||||
)
|
||||
)
|
||||
|
||||
assert patched["revision"] == 2
|
||||
assert patched["status"] == "valid"
|
||||
|
||||
|
||||
def test_workflow_surface_creates_minimal_draft_workspace_with_error_route() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_minimal_workspace"
|
||||
|
||||
Reference in New Issue
Block a user