list deployment compaction & inspect deployment
This commit is contained in:
+44
-26
@@ -1,46 +1,62 @@
|
|||||||
# Current Roadmap
|
# Current Roadmap
|
||||||
|
|
||||||
This is the short active roadmap after the core type-shape cleanup. It is based
|
This is the short active roadmap after the core type-shape cleanup and MCP
|
||||||
on both the current docs and the implementation state.
|
workflow authoring cleanup pass. It is based on both the current docs and the
|
||||||
|
implementation state.
|
||||||
|
|
||||||
## Next Work
|
## Completed Cleanup Pass
|
||||||
|
|
||||||
1. **Docs index and prune**
|
1. **Docs index and prune**
|
||||||
- Add/maintain a clear docs entry point.
|
- Current architecture docs are separated from historical plans and scratch
|
||||||
- Keep current architecture docs separate from historical plans and scratch
|
|
||||||
notes.
|
notes.
|
||||||
|
- The active roadmap now lives here instead of being scattered through older
|
||||||
|
planning files.
|
||||||
|
|
||||||
2. **MCP workflow authoring UX**
|
2. **MCP workflow authoring UX**
|
||||||
- Make the LLM/client path progressive: inspect sources, create a draft,
|
- The operator manual categorizes workflow tools into discovery, draft
|
||||||
patch, validate, compile, save, run.
|
workspace, stateless draft, artifact/deployment, run/debug, and raw escape
|
||||||
- Prefer smaller discovery/inspection responses over one huge payload.
|
|
||||||
- Done: the operator manual now categorizes workflow tools into discovery,
|
|
||||||
draft workspace, stateless draft, artifact/deployment, and raw escape
|
|
||||||
hatch groups.
|
hatch groups.
|
||||||
- Next: tighten the actual tool responses around that map so list calls stay
|
- List-style tools are more compact, while inspect/run tools carry the
|
||||||
compact and inspect/run calls carry the detailed payloads.
|
detailed payloads.
|
||||||
|
|
||||||
3. **Wrapper creation ergonomics**
|
3. **Wrapper creation ergonomics**
|
||||||
- Help create workflow-ready wrappers from raw capabilities.
|
- Wrapper draft helpers can suggest state schema, input bindings, output
|
||||||
- Suggest state schema, input bindings, output bindings, default `ok` /
|
bindings, default `ok` / `error` handling, and missing decisions.
|
||||||
`error` handling, and missing decisions.
|
- The end-to-end runbook documents the wrapper path from capability
|
||||||
|
discovery through deployment/run.
|
||||||
|
|
||||||
4. **Run and deployment story**
|
4. **Run and deployment story**
|
||||||
- Tighten list/inspect/run/debug for artifacts and deployments.
|
- Deployment listing is summary-first, with dedicated inspection for detail.
|
||||||
- Keep dependency validation and trace/error output compact and actionable.
|
- `run_deployment` returns compact status by default and exposes trace slices
|
||||||
|
through an explicit `trace_range`.
|
||||||
|
- Dependency validation and error output remain part of the run path.
|
||||||
|
|
||||||
5. **Source inventory polish**
|
5. **Source inventory polish**
|
||||||
- Make `list_sources` / `inspect_source` clearly show raw capabilities,
|
- `list_sources` / `inspect_source` now present source-owned capabilities
|
||||||
workflow-ready node specs, admin-only tools, docs/resources, enabled state,
|
progressively.
|
||||||
and changes after reload.
|
- Source inventory distinguishes external sources, local workflow-facing
|
||||||
|
sources, docs/resources, and admin-only control surfaces.
|
||||||
|
|
||||||
## Runtime Work To Revisit Later
|
## Runtime and Platform Roadmap
|
||||||
|
|
||||||
- **Native subgraphs**: add child run state, child trace preservation, interrupt
|
- **Native subgraphs / graph-as-node**: add child run state, child trace
|
||||||
bubbling, and resume back into the child workflow.
|
preservation, interrupt bubbling, and resume back into the child workflow.
|
||||||
|
Wrapper artifacts currently execute as deployments and return run status;
|
||||||
|
true graph-as-node outcome propagation belongs here.
|
||||||
- **Async parallel foreach**: add explicit scheduling, reducer/merge semantics,
|
- **Async parallel foreach**: add explicit scheduling, reducer/merge semantics,
|
||||||
and failure policy. Do not model this as plain parallel calls over sync
|
and failure policy. Do not model this as plain parallel calls over sync
|
||||||
handlers.
|
handlers.
|
||||||
|
- **Persistent run history**: add a run store before adding stable `run_id`,
|
||||||
|
`inspect_run`, or `read_run_trace(run_id, range)` APIs. Current traces are
|
||||||
|
returned directly from immediate run responses.
|
||||||
|
- **Protocol-native long-running runs**: investigate MCP tasks/progress
|
||||||
|
notifications for long-running workflow execution. Avoid inventing a custom
|
||||||
|
"start" convention unless protocol-native behavior is insufficient.
|
||||||
|
- **Dynamic saved workflows as tools**: defer until the stable run/inspect
|
||||||
|
surface is strong. Many MCP clients do not refresh tool lists reliably, so
|
||||||
|
`wf.workflow.run_deployment` remains the dependable front door.
|
||||||
|
- **Dashboard/source controls**: future UI should consume the same source
|
||||||
|
inventory and deployment metadata instead of reverse-engineering MCP tools.
|
||||||
|
|
||||||
Frame stress points to solve before either feature:
|
Frame stress points to solve before either feature:
|
||||||
|
|
||||||
@@ -57,6 +73,8 @@ Frame stress points to solve before either feature:
|
|||||||
|
|
||||||
## Why This Order
|
## Why This Order
|
||||||
|
|
||||||
`wf_core` is now coherent enough for the next bottleneck to be platform and DX:
|
The MCP workflow authoring path is now usable enough for real testing. The next
|
||||||
how a human or LLM discovers capabilities, turns them into workflow-ready
|
bottleneck is runtime/platform correctness: resumable child execution,
|
||||||
pieces, saves them, and runs them again.
|
parallel scheduling, persistent run history, and protocol-native progress
|
||||||
|
reporting. Those pieces should come before adding more high-level authoring
|
||||||
|
sugar.
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ Supporting:
|
|||||||
- `wf.workflow.list_artifacts`: compact list of saved workflow and wrapper
|
- `wf.workflow.list_artifacts`: compact list of saved workflow and wrapper
|
||||||
artifacts.
|
artifacts.
|
||||||
- `wf.workflow.inspect_artifact`: full saved artifact payload.
|
- `wf.workflow.inspect_artifact`: full saved artifact payload.
|
||||||
|
- `wf.workflow.list_deployments`: compact list of saved deployment summaries.
|
||||||
|
- `wf.workflow.inspect_deployment`: full deployment payload including source
|
||||||
|
bindings.
|
||||||
|
|
||||||
### Draft Workspaces
|
### Draft Workspaces
|
||||||
|
|
||||||
@@ -173,6 +176,8 @@ Primary:
|
|||||||
|
|
||||||
- `wf.workflow.save_deployment`: bind one saved artifact version to concrete
|
- `wf.workflow.save_deployment`: bind one saved artifact version to concrete
|
||||||
sources.
|
sources.
|
||||||
|
- `wf.workflow.inspect_deployment`: inspect source bindings for one saved
|
||||||
|
deployment.
|
||||||
- `wf.workflow.validate_deployment`: check dependency availability and drift.
|
- `wf.workflow.validate_deployment`: check dependency availability and drift.
|
||||||
- `wf.workflow.run_deployment`: execute a saved deployment with input. The
|
- `wf.workflow.run_deployment`: execute a saved deployment with input. The
|
||||||
default response is compact and returns `trace_count`; pass `trace_range`
|
default response is compact and returns `trace_count`; pass `trace_range`
|
||||||
@@ -372,6 +377,9 @@ data.
|
|||||||
| Save a workflow definition from a draft | `wf.workflow.create_artifact_from_draft` |
|
| Save a workflow definition from a draft | `wf.workflow.create_artifact_from_draft` |
|
||||||
| Save a compiled raw workflow definition | `wf.workflow.create_artifact_from_plan` |
|
| Save a compiled raw workflow definition | `wf.workflow.create_artifact_from_plan` |
|
||||||
| List saved workflows/wrappers | `wf.workflow.list_artifacts` |
|
| List saved workflows/wrappers | `wf.workflow.list_artifacts` |
|
||||||
|
| Inspect one saved workflow/wrapper | `wf.workflow.inspect_artifact` |
|
||||||
|
| List saved deployments | `wf.workflow.list_deployments` |
|
||||||
|
| Inspect one saved deployment | `wf.workflow.inspect_deployment` |
|
||||||
| Bind a saved workflow to concrete sources | `wf.workflow.save_deployment` |
|
| Bind a saved workflow to concrete sources | `wf.workflow.save_deployment` |
|
||||||
| Check whether a deployment can run | `wf.workflow.validate_deployment` |
|
| Check whether a deployment can run | `wf.workflow.validate_deployment` |
|
||||||
| Execute a saved workflow | `wf.workflow.run_deployment` |
|
| Execute a saved workflow | `wf.workflow.run_deployment` |
|
||||||
|
|||||||
@@ -69,6 +69,21 @@ semantics.
|
|||||||
Dynamic projection of saved workflows as individual MCP tools can exist later,
|
Dynamic projection of saved workflows as individual MCP tools can exist later,
|
||||||
but it should be optional. The stable run tool is the reliable base layer.
|
but it should be optional. The stable run tool is the reliable base layer.
|
||||||
|
|
||||||
|
Current `run_deployment` calls are synchronous request/response executions. They
|
||||||
|
return compact status, output, diagnostics, and `trace_count`; optional ranged
|
||||||
|
trace detail is for debugging only.
|
||||||
|
|
||||||
|
Future run history should introduce a stable `run_id` only when there is a real
|
||||||
|
run store behind it. A `run_id` without persisted state, trace paging, and
|
||||||
|
status lookup would be misleading. The likely shape is:
|
||||||
|
|
||||||
|
- `run_deployment` starts or completes a run and returns `run_id`
|
||||||
|
- `inspect_run(run_id)` returns status, output, diagnostics, and trace metadata
|
||||||
|
- `read_run_trace(run_id, range)` returns bounded trace slices
|
||||||
|
|
||||||
|
Until that exists, clients should treat the current response as the complete
|
||||||
|
ephemeral run result for this request.
|
||||||
|
|
||||||
For long-running workflow execution, prefer MCP-native execution mechanisms
|
For long-running workflow execution, prefer MCP-native execution mechanisms
|
||||||
where available:
|
where available:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Annotated, Any
|
||||||
|
|
||||||
|
from pydantic import Field
|
||||||
|
|
||||||
from wf_mcp.broker.service import WfMcpService
|
from wf_mcp.broker.service import WfMcpService
|
||||||
|
|
||||||
@@ -84,8 +86,26 @@ def register_service_admin_tools(
|
|||||||
description="List compact configured capability source summaries.",
|
description="List compact configured capability source summaries.",
|
||||||
)
|
)
|
||||||
async def list_sources(
|
async def list_sources(
|
||||||
cursor: str | None = None,
|
cursor: Annotated[
|
||||||
limit: int = 50,
|
str | None,
|
||||||
|
Field(
|
||||||
|
description=(
|
||||||
|
"Opaque pagination cursor returned by a previous list_sources "
|
||||||
|
"call. Omit for the first page."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
] = None,
|
||||||
|
limit: Annotated[
|
||||||
|
int,
|
||||||
|
Field(
|
||||||
|
ge=1,
|
||||||
|
le=100,
|
||||||
|
description=(
|
||||||
|
"Maximum source summaries to return. Use inspect_source for "
|
||||||
|
"one full source inventory."
|
||||||
|
),
|
||||||
|
),
|
||||||
|
] = 50,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
return handlers.list_sources(cursor=cursor, limit=limit)
|
return handlers.list_sources(cursor=cursor, limit=limit)
|
||||||
|
|
||||||
@@ -94,7 +114,17 @@ def register_service_admin_tools(
|
|||||||
title="Inspect Source",
|
title="Inspect Source",
|
||||||
description="Return the full inventory for one configured capability source.",
|
description="Return the full inventory for one configured capability source.",
|
||||||
)
|
)
|
||||||
async def inspect_source(source_id: str) -> dict[str, Any]:
|
async def inspect_source(
|
||||||
|
source_id: Annotated[
|
||||||
|
str,
|
||||||
|
Field(
|
||||||
|
description=(
|
||||||
|
"Exact source id from list_sources, such as wf.std, wf.docs, "
|
||||||
|
"or an enabled connection id like demo.personal."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
) -> dict[str, Any]:
|
||||||
return handlers.inspect_source(source_id)
|
return handlers.inspect_source(source_id)
|
||||||
|
|
||||||
@server.tool(
|
@server.tool(
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ _SEARCH_ALWAYS_VISIBLE_TOOL_NAMES = [
|
|||||||
"wf.workflow.create_wrapper_from_workspace",
|
"wf.workflow.create_wrapper_from_workspace",
|
||||||
"wf.workflow.inspect_artifact",
|
"wf.workflow.inspect_artifact",
|
||||||
"wf.workflow.list_deployments",
|
"wf.workflow.list_deployments",
|
||||||
|
"wf.workflow.inspect_deployment",
|
||||||
"wf.workflow.save_deployment",
|
"wf.workflow.save_deployment",
|
||||||
"wf.workflow.validate_deployment",
|
"wf.workflow.validate_deployment",
|
||||||
"wf.workflow.run_deployment",
|
"wf.workflow.run_deployment",
|
||||||
|
|||||||
@@ -819,11 +819,18 @@ class WorkflowSurfaceHandlers:
|
|||||||
return {"deployments": []}
|
return {"deployments": []}
|
||||||
return {
|
return {
|
||||||
"deployments": [
|
"deployments": [
|
||||||
deployment.model_dump(mode="json")
|
_deployment_summary(deployment)
|
||||||
for deployment in self.service.artifact_store.list_deployments()
|
for deployment in self.service.artifact_store.list_deployments()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async def inspect_deployment(self, *, deployment_id: str) -> dict[str, Any]:
|
||||||
|
if self.service.artifact_store is None:
|
||||||
|
raise KeyError("workflow artifact store is not configured")
|
||||||
|
return self.service.artifact_store.get_deployment(deployment_id).model_dump(
|
||||||
|
mode="json"
|
||||||
|
)
|
||||||
|
|
||||||
async def save_deployment(self, deployment: dict[str, Any]) -> dict[str, Any]:
|
async def save_deployment(self, deployment: dict[str, Any]) -> dict[str, Any]:
|
||||||
if self.service.artifact_store is None:
|
if self.service.artifact_store is None:
|
||||||
raise KeyError("workflow artifact store is not configured")
|
raise KeyError("workflow artifact store is not configured")
|
||||||
@@ -1262,3 +1269,14 @@ def _run_payload(
|
|||||||
payload["trace"] = trace
|
payload["trace"] = trace
|
||||||
payload["trace_truncated"] = trace_truncated
|
payload["trace_truncated"] = trace_truncated
|
||||||
return payload
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def _deployment_summary(deployment: WorkflowDeployment) -> dict[str, Any]:
|
||||||
|
"""Return compact deployment metadata for progressive list responses."""
|
||||||
|
return {
|
||||||
|
"id": deployment.id,
|
||||||
|
"artifact_id": deployment.artifact_id,
|
||||||
|
"artifact_version": deployment.artifact_version,
|
||||||
|
"binding_count": len(deployment.binding_map()),
|
||||||
|
"drift_policy": deployment.drift_policy.value,
|
||||||
|
}
|
||||||
|
|||||||
@@ -514,11 +514,22 @@ def register_workflow_tools(server: FastMCP[Any], service: WfMcpService) -> None
|
|||||||
@server.tool(
|
@server.tool(
|
||||||
name="wf.workflow.list_deployments",
|
name="wf.workflow.list_deployments",
|
||||||
title="List Workflow Deployments",
|
title="List Workflow Deployments",
|
||||||
description="List saved workflow deployments and their source bindings.",
|
description=(
|
||||||
|
"List compact saved workflow deployment summaries. Use "
|
||||||
|
"inspect_deployment for full source bindings."
|
||||||
|
),
|
||||||
)
|
)
|
||||||
async def list_deployments() -> dict[str, Any]:
|
async def list_deployments() -> dict[str, Any]:
|
||||||
return await handlers.list_deployments()
|
return await handlers.list_deployments()
|
||||||
|
|
||||||
|
@server.tool(
|
||||||
|
name="wf.workflow.inspect_deployment",
|
||||||
|
title="Inspect Workflow Deployment",
|
||||||
|
description="Return one full workflow deployment including source bindings.",
|
||||||
|
)
|
||||||
|
async def inspect_deployment(deployment_id: str) -> dict[str, Any]:
|
||||||
|
return await handlers.inspect_deployment(deployment_id=deployment_id)
|
||||||
|
|
||||||
@server.tool(
|
@server.tool(
|
||||||
name="wf.workflow.save_deployment",
|
name="wf.workflow.save_deployment",
|
||||||
title="Save Workflow Deployment",
|
title="Save Workflow Deployment",
|
||||||
|
|||||||
+19
-11
@@ -10,6 +10,7 @@ from wf_platform.refs import CapabilityRef
|
|||||||
|
|
||||||
SourceKind = Literal["system", "connection"]
|
SourceKind = Literal["system", "connection"]
|
||||||
JsonObject = dict[str, Any]
|
JsonObject = dict[str, Any]
|
||||||
|
SOURCE_PREVIEW_LIMIT = 3
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -143,7 +144,6 @@ class CapabilitySource:
|
|||||||
|
|
||||||
def as_status(self) -> SourceStatus:
|
def as_status(self) -> SourceStatus:
|
||||||
"""Return serializable source metadata without owned capability names."""
|
"""Return serializable source metadata without owned capability names."""
|
||||||
preview_limit = 3
|
|
||||||
return SourceStatus(
|
return SourceStatus(
|
||||||
id=self.id,
|
id=self.id,
|
||||||
kind=self.kind,
|
kind=self.kind,
|
||||||
@@ -166,21 +166,29 @@ class CapabilitySource:
|
|||||||
prompt_count=len(self.capabilities.prompts),
|
prompt_count=len(self.capabilities.prompts),
|
||||||
resource_count=len(self.capabilities.resources),
|
resource_count=len(self.capabilities.resources),
|
||||||
preview=SourceCapabilityPreview(
|
preview=SourceCapabilityPreview(
|
||||||
tools=_preview_names(self.capabilities.tools, preview_limit),
|
tools=_preview_names(self.capabilities.tools, SOURCE_PREVIEW_LIMIT),
|
||||||
node_specs=_preview_names(
|
node_specs=_preview_names(
|
||||||
self.capabilities.node_specs,
|
self.capabilities.node_specs,
|
||||||
preview_limit,
|
SOURCE_PREVIEW_LIMIT,
|
||||||
|
),
|
||||||
|
reducers=_preview_names(
|
||||||
|
self.capabilities.reducers, SOURCE_PREVIEW_LIMIT
|
||||||
|
),
|
||||||
|
prompts=_preview_names(
|
||||||
|
self.capabilities.prompts, SOURCE_PREVIEW_LIMIT
|
||||||
|
),
|
||||||
|
resources=_preview_names(
|
||||||
|
self.capabilities.resources, SOURCE_PREVIEW_LIMIT
|
||||||
),
|
),
|
||||||
reducers=_preview_names(self.capabilities.reducers, preview_limit),
|
|
||||||
prompts=_preview_names(self.capabilities.prompts, preview_limit),
|
|
||||||
resources=_preview_names(self.capabilities.resources, preview_limit),
|
|
||||||
),
|
),
|
||||||
has_more=SourceCapabilityHasMore(
|
has_more=SourceCapabilityHasMore(
|
||||||
tools=_has_more(self.capabilities.tools, preview_limit),
|
tools=_has_more(self.capabilities.tools, SOURCE_PREVIEW_LIMIT),
|
||||||
node_specs=_has_more(self.capabilities.node_specs, preview_limit),
|
node_specs=_has_more(
|
||||||
reducers=_has_more(self.capabilities.reducers, preview_limit),
|
self.capabilities.node_specs, SOURCE_PREVIEW_LIMIT
|
||||||
prompts=_has_more(self.capabilities.prompts, preview_limit),
|
),
|
||||||
resources=_has_more(self.capabilities.resources, preview_limit),
|
reducers=_has_more(self.capabilities.reducers, SOURCE_PREVIEW_LIMIT),
|
||||||
|
prompts=_has_more(self.capabilities.prompts, SOURCE_PREVIEW_LIMIT),
|
||||||
|
resources=_has_more(self.capabilities.resources, SOURCE_PREVIEW_LIMIT),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -132,6 +132,14 @@ def test_server_exposes_upstream_admin_and_workflow_tools() -> None:
|
|||||||
assert create_workspace_schema is not None
|
assert create_workspace_schema is not None
|
||||||
assert "workspace_id" in create_workspace_schema["properties"]
|
assert "workspace_id" in create_workspace_schema["properties"]
|
||||||
assert "revision" in create_workspace_schema["properties"]
|
assert "revision" in create_workspace_schema["properties"]
|
||||||
|
list_sources_schema = tools_by_name["wf.admin.list_sources"].inputSchema
|
||||||
|
assert "inspect_source" in list_sources_schema["properties"]["limit"][
|
||||||
|
"description"
|
||||||
|
]
|
||||||
|
inspect_source_schema = tools_by_name["wf.admin.inspect_source"].inputSchema
|
||||||
|
assert "Exact source id" in inspect_source_schema["properties"][
|
||||||
|
"source_id"
|
||||||
|
]["description"]
|
||||||
minimal_workspace_input = tools_by_name[
|
minimal_workspace_input = tools_by_name[
|
||||||
"wf.workflow.create_minimal_draft_workspace"
|
"wf.workflow.create_minimal_draft_workspace"
|
||||||
].inputSchema
|
].inputSchema
|
||||||
@@ -274,6 +282,7 @@ def test_server_search_mode_pins_stable_control_and_workflow_tools() -> None:
|
|||||||
assert "wf.workflow.create_wrapper_from_workspace" in names
|
assert "wf.workflow.create_wrapper_from_workspace" in names
|
||||||
assert "wf.workflow.inspect_artifact" in names
|
assert "wf.workflow.inspect_artifact" in names
|
||||||
assert "wf.workflow.list_deployments" in names
|
assert "wf.workflow.list_deployments" in names
|
||||||
|
assert "wf.workflow.inspect_deployment" in names
|
||||||
assert "wf.workflow.save_deployment" in names
|
assert "wf.workflow.save_deployment" in names
|
||||||
assert "wf.workflow.validate_deployment" in names
|
assert "wf.workflow.validate_deployment" in names
|
||||||
assert "wf.workflow.run_deployment" in names
|
assert "wf.workflow.run_deployment" in names
|
||||||
|
|||||||
@@ -268,6 +268,33 @@ def test_workflow_surface_records_artifact_and_deployment_save_events() -> None:
|
|||||||
assert events[1].capability_id == "deployment.echo.personal"
|
assert events[1].capability_id == "deployment.echo.personal"
|
||||||
|
|
||||||
|
|
||||||
|
def test_workflow_surface_lists_compact_deployment_summaries_and_inspects_detail() -> (
|
||||||
|
None
|
||||||
|
):
|
||||||
|
artifact_store = FileWorkflowArtifactStore(
|
||||||
|
local_temp_root() / "surface_deployment_list"
|
||||||
|
)
|
||||||
|
handlers = _handlers(artifact_store)
|
||||||
|
deployment = WorkflowDeployment(
|
||||||
|
id="echo.personal",
|
||||||
|
artifact_id="echo",
|
||||||
|
artifact_version=1,
|
||||||
|
bindings=[{"logical_source": "demo", "concrete_source": "demo.personal"}],
|
||||||
|
)
|
||||||
|
artifact_store.save_deployment(deployment)
|
||||||
|
|
||||||
|
listed = asyncio.run(handlers.list_deployments())
|
||||||
|
inspected = asyncio.run(
|
||||||
|
handlers.inspect_deployment(deployment_id="echo.personal")
|
||||||
|
)
|
||||||
|
|
||||||
|
assert listed["deployments"][0]["id"] == "echo.personal"
|
||||||
|
assert listed["deployments"][0]["binding_count"] == 1
|
||||||
|
assert "bindings" not in listed["deployments"][0]
|
||||||
|
assert inspected["bindings"][0]["logical_source"] == "demo"
|
||||||
|
assert inspected["bindings"][0]["concrete_source"] == "demo.personal"
|
||||||
|
|
||||||
|
|
||||||
def test_workflow_surface_creates_wrapper_artifact_from_plan() -> None:
|
def test_workflow_surface_creates_wrapper_artifact_from_plan() -> None:
|
||||||
artifact_store = FileWorkflowArtifactStore(
|
artifact_store = FileWorkflowArtifactStore(
|
||||||
local_temp_root() / "surface_wrapper_plan"
|
local_temp_root() / "surface_wrapper_plan"
|
||||||
|
|||||||
Reference in New Issue
Block a user