feat: type draft workspace results
This commit is contained in:
@@ -71,12 +71,13 @@
|
||||
- A 2026-07-30 spike confirmed that `fastapi-jsonrpc` already exports a
|
||||
complete OpenRPC document for all 70 registered methods. Request payloads
|
||||
retain useful Pydantic schemas, so OpenRPC is a viable transport input.
|
||||
- Typed-result slices now give `workflow.health` and all artifact, deployment,
|
||||
and run operations named transport-neutral result schemas: 16 of 70
|
||||
methods. The remaining 54 success results still collapse to generic objects
|
||||
because their Python API and JSON-RPC handlers return `dict[str, Any]`.
|
||||
Continue introducing operation result DTOs before adopting generated
|
||||
TypeScript contracts.
|
||||
- Typed-result slices now give `workflow.health`, all artifact, deployment,
|
||||
and run operations, and 26 uniform persisted draft-workspace operations
|
||||
named transport-neutral result schemas: 42 of 70 methods. The remaining 28
|
||||
success results still collapse to generic objects, including exceptional
|
||||
draft compile/save shapes and capability/source/admin operations. Continue
|
||||
introducing operation result DTOs before adopting generated TypeScript
|
||||
contracts.
|
||||
- The stock `@open-rpc/generator` TypeScript client is not suitable here. It
|
||||
exhausted a 4 GB Node heap on the full contract and emitted invalid dotted
|
||||
class members plus `any` results for a minimal `workflow.health` contract.
|
||||
|
||||
@@ -58,6 +58,7 @@ from .drafts import (
|
||||
_draft_input_maps,
|
||||
_draft_output_map,
|
||||
)
|
||||
from .models import DraftWorkspaceResult, JsonProjector
|
||||
from .operation_context import WorkflowOperationContext
|
||||
from .schema_projection import (
|
||||
project_output_property_to_state_schema,
|
||||
@@ -67,6 +68,8 @@ from .schema_projection import (
|
||||
validate_json_value_at_schema_path,
|
||||
)
|
||||
|
||||
_PROJECT_DRAFT_WORKSPACE = JsonProjector(DraftWorkspaceResult)
|
||||
|
||||
|
||||
def _graph_parts(path: str) -> tuple[str, tuple[str, ...]]:
|
||||
parsed = GraphSourcePath.parse(path)
|
||||
@@ -322,7 +325,7 @@ class WorkflowDraftAuthoringApi:
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
) -> WorkflowDraftWorkspace | dict[str, Any]:
|
||||
) -> WorkflowDraftWorkspace | DraftWorkspaceResult:
|
||||
"""Load a workspace and enforce optimistic locking before semantic preflight."""
|
||||
return self.drafts._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -367,7 +370,7 @@ class WorkflowDraftAuthoringApi:
|
||||
step: DraftStep,
|
||||
incoming: RouteSource | None = None,
|
||||
routes: dict[str, str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Add one typed draft step and optional route edits in one revision."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -471,7 +474,7 @@ class WorkflowDraftAuthoringApi:
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: str | GraphSourcePath | None = None,
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Bootstrap the smallest patchable draft around one workflow capability."""
|
||||
draft_input, draft_with = _draft_input_maps(
|
||||
input=input,
|
||||
@@ -530,7 +533,7 @@ class WorkflowDraftAuthoringApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
bindings: Sequence[InputBinding],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Replace one capability step's canonical input bindings atomically."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -556,7 +559,7 @@ class WorkflowDraftAuthoringApi:
|
||||
and workspace.draft.get("input_schema", {}) == projected.input_schema
|
||||
and workspace.draft.get("state_schema", {}) == projected.state_schema
|
||||
):
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
|
||||
patch = _step_input_bindings_patch(
|
||||
workspace=workspace,
|
||||
@@ -651,7 +654,7 @@ class WorkflowDraftAuthoringApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
update: CapabilityStepUpdate,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Return a workspace summary or conflict after one atomic step patch."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -718,7 +721,7 @@ class WorkflowDraftAuthoringApi:
|
||||
and workspace.draft.get("input_schema", {}) == input_schema
|
||||
and workspace.draft.get("state_schema", {}) == state_schema
|
||||
):
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
|
||||
next_draft = deepcopy(workspace.draft)
|
||||
next_steps = next_draft.get("steps")
|
||||
@@ -739,7 +742,7 @@ class WorkflowDraftAuthoringApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
bindings: Sequence[InputBinding],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Replace canonical workflow output bindings atomically."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -839,7 +842,7 @@ class WorkflowDraftAuthoringApi:
|
||||
|
||||
payload = [binding.model_dump(mode="json") for binding in bindings]
|
||||
if workspace.draft.get("output", []) == payload and projected == output_schema:
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
|
||||
patch: list[dict[str, Any]] = []
|
||||
if projected != output_schema:
|
||||
@@ -864,7 +867,7 @@ class WorkflowDraftAuthoringApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
bindings: Sequence[OutputBinding],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Replace one capability step's canonical output bindings atomically."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -924,7 +927,7 @@ class WorkflowDraftAuthoringApi:
|
||||
step.get("output", []) == payload
|
||||
and workspace.draft.get("state_schema", {}) == projected_state
|
||||
):
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
|
||||
return await self.drafts.patch_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
@@ -945,7 +948,7 @@ class WorkflowDraftAuthoringApi:
|
||||
step_id: str,
|
||||
source_path: str,
|
||||
target_path: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Bind a graph path to or from one capability-local path."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -1141,7 +1144,7 @@ class WorkflowDraftAuthoringApi:
|
||||
desc: str | None = None,
|
||||
retry: int | None = None,
|
||||
timeout_seconds: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Add one capability step plus explicit route/map/schema wiring.
|
||||
|
||||
This is a composed authoring helper for agents. It edits the draft in
|
||||
@@ -1321,7 +1324,7 @@ class WorkflowDraftAuthoringApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
routes: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Atomically set routes for one step, preserving unspecified outcomes."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -1338,7 +1341,7 @@ class WorkflowDraftAuthoringApi:
|
||||
raise ValueError(f"routes for step {step_id!r} must be an object")
|
||||
merged = {**existing, **routes}
|
||||
if merged == existing:
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
return await self.drafts.patch_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -1358,7 +1361,7 @@ class WorkflowDraftAuthoringApi:
|
||||
revision: int,
|
||||
branches: Sequence[RouteSource],
|
||||
target: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Update the target for multiple (step, outcome) pairs atomically."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -1368,7 +1371,7 @@ class WorkflowDraftAuthoringApi:
|
||||
return checked
|
||||
workspace = checked
|
||||
if not branches:
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
draft_routes = workspace.draft.get("routes", {})
|
||||
if not isinstance(draft_routes, dict):
|
||||
raise ValueError("draft routes must be an object")
|
||||
@@ -1397,7 +1400,7 @@ class WorkflowDraftAuthoringApi:
|
||||
}
|
||||
)
|
||||
if not patch:
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
return await self.drafts.patch_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -1411,7 +1414,7 @@ class WorkflowDraftAuthoringApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Remove one route; missing routes are revision-checked no-ops."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -1427,7 +1430,7 @@ class WorkflowDraftAuthoringApi:
|
||||
if not isinstance(step_routes, dict):
|
||||
raise ValueError(f"routes for step {step_id!r} must be an object")
|
||||
if outcome not in step_routes:
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
return await self.drafts.patch_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -1448,7 +1451,7 @@ class WorkflowDraftAuthoringApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Remove a step and its own route map; inbound routes are left explicit."""
|
||||
checked = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -1461,7 +1464,7 @@ class WorkflowDraftAuthoringApi:
|
||||
if not isinstance(steps, dict):
|
||||
raise ValueError("draft steps must be an object")
|
||||
if step_id not in steps:
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
patch = [
|
||||
{
|
||||
"op": "remove",
|
||||
@@ -1490,7 +1493,7 @@ class WorkflowDraftAuthoringApi:
|
||||
step_id: str,
|
||||
inputs: Sequence[str] = (),
|
||||
outputs: Sequence[str] = (),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Remove selected local input/output bindings from one draft step."""
|
||||
if not inputs and not outputs:
|
||||
raise ValueError("pass at least one input or output binding to remove")
|
||||
@@ -1525,7 +1528,7 @@ class WorkflowDraftAuthoringApi:
|
||||
item for item in current_outputs if item.get("source") not in output_sources
|
||||
]
|
||||
if next_inputs == current_inputs and next_outputs == current_outputs:
|
||||
return summarize_draft_workspace(workspace)
|
||||
return _PROJECT_DRAFT_WORKSPACE(summarize_draft_workspace(workspace))
|
||||
patch: list[dict[str, Any]] = []
|
||||
if next_inputs != current_inputs:
|
||||
patch.append(
|
||||
|
||||
+101
-69
@@ -52,9 +52,19 @@ from .draft_payloads import (
|
||||
from .draft_payloads import (
|
||||
output_bindings_payload as _draft_output_bindings_payload,
|
||||
)
|
||||
from .models import (
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
JsonProjector,
|
||||
ListDraftWorkspacesResult,
|
||||
)
|
||||
from .operation_context import WorkflowOperationContext
|
||||
from .schema_projection import project_property_to_schema_path, schema_path_exists
|
||||
|
||||
_PROJECT_DRAFT_WORKSPACE = JsonProjector(DraftWorkspaceResult)
|
||||
_PROJECT_DRAFT_WORKSPACE_LIST = JsonProjector(ListDraftWorkspacesResult)
|
||||
_PROJECT_DRAFT_WORKSPACE_DELETE = JsonProjector(DeleteDraftWorkspaceResult)
|
||||
|
||||
|
||||
def _empty_object_schema() -> dict[str, Any]:
|
||||
"""Return one fresh unconstrained object schema for an empty draft."""
|
||||
@@ -108,25 +118,27 @@ class WorkflowDraftApi:
|
||||
*,
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
) -> WorkflowDraftWorkspace | dict[str, Any]:
|
||||
) -> WorkflowDraftWorkspace | DraftWorkspaceResult:
|
||||
"""Load a workspace or return its canonical revision-conflict payload."""
|
||||
workspace = self._draft_store().get_workspace(workspace_id)
|
||||
if workspace.revision == revision:
|
||||
return workspace
|
||||
return {
|
||||
**summarize_draft_workspace(workspace),
|
||||
"status": "conflict",
|
||||
"diagnostics": [
|
||||
{
|
||||
"code": "revision_conflict",
|
||||
"path": "revision",
|
||||
"message": (
|
||||
f"workspace {workspace.id!r} is at revision "
|
||||
f"{workspace.revision}, not {revision}"
|
||||
),
|
||||
}
|
||||
],
|
||||
}
|
||||
return _PROJECT_DRAFT_WORKSPACE(
|
||||
{
|
||||
**summarize_draft_workspace(workspace),
|
||||
"status": "conflict",
|
||||
"diagnostics": [
|
||||
{
|
||||
"code": "revision_conflict",
|
||||
"path": "revision",
|
||||
"message": (
|
||||
f"workspace {workspace.id!r} is at revision "
|
||||
f"{workspace.revision}, not {revision}"
|
||||
),
|
||||
}
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
def _outcomes_for_capability(self, qualified_name: str) -> tuple[str, ...] | None:
|
||||
try:
|
||||
@@ -189,15 +201,17 @@ class WorkflowDraftApi:
|
||||
node_defs_for_draft=self._node_defs_for_draft,
|
||||
)
|
||||
|
||||
async def list_draft_workspaces(self) -> dict[str, Any]:
|
||||
async def list_draft_workspaces(self) -> ListDraftWorkspacesResult:
|
||||
"""Return compact summaries for stored draft workspaces."""
|
||||
store = self._draft_store()
|
||||
return {
|
||||
"workspaces": [
|
||||
get_draft_workspace_record(store, workspace_id=workspace.id)
|
||||
for workspace in store.list_workspaces()
|
||||
]
|
||||
}
|
||||
return _PROJECT_DRAFT_WORKSPACE_LIST(
|
||||
{
|
||||
"workspaces": [
|
||||
get_draft_workspace_record(store, workspace_id=workspace.id)
|
||||
for workspace in store.list_workspaces()
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
async def create_draft_workspace(
|
||||
self,
|
||||
@@ -205,12 +219,14 @@ class WorkflowDraftApi:
|
||||
workspace_id: str,
|
||||
draft: dict[str, Any],
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return create_draft_workspace_record(
|
||||
self._draft_store(),
|
||||
workspace_id=workspace_id,
|
||||
draft=draft,
|
||||
title=title,
|
||||
) -> DraftWorkspaceResult:
|
||||
return _PROJECT_DRAFT_WORKSPACE(
|
||||
create_draft_workspace_record(
|
||||
self._draft_store(),
|
||||
workspace_id=workspace_id,
|
||||
draft=draft,
|
||||
title=title,
|
||||
)
|
||||
)
|
||||
|
||||
async def create_empty_draft_workspace(
|
||||
@@ -223,7 +239,7 @@ class WorkflowDraftApi:
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
outcomes: Sequence[str] = ("ok",),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Create an intentionally invalid, capability-free draft workspace.
|
||||
|
||||
The empty entry point is persisted so callers can assemble the graph in
|
||||
@@ -272,22 +288,30 @@ class WorkflowDraftApi:
|
||||
*,
|
||||
workspace_id: str,
|
||||
include_draft: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return get_draft_workspace_record(
|
||||
self._draft_store(),
|
||||
workspace_id=workspace_id,
|
||||
include_draft=include_draft,
|
||||
) -> DraftWorkspaceResult:
|
||||
return _PROJECT_DRAFT_WORKSPACE(
|
||||
get_draft_workspace_record(
|
||||
self._draft_store(),
|
||||
workspace_id=workspace_id,
|
||||
include_draft=include_draft,
|
||||
)
|
||||
)
|
||||
|
||||
async def delete_draft_workspace(self, *, workspace_id: str) -> dict[str, Any]:
|
||||
async def delete_draft_workspace(
|
||||
self, *, workspace_id: str
|
||||
) -> DeleteDraftWorkspaceResult:
|
||||
deleted = self._draft_store().delete_workspace(workspace_id)
|
||||
return {
|
||||
"workspace_id": workspace_id,
|
||||
"deleted": deleted,
|
||||
"status": "deleted" if deleted else "not_found",
|
||||
}
|
||||
return _PROJECT_DRAFT_WORKSPACE_DELETE(
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
"deleted": deleted,
|
||||
"status": "deleted" if deleted else "not_found",
|
||||
}
|
||||
)
|
||||
|
||||
async def validate_draft_workspace(self, *, workspace_id: str) -> dict[str, Any]:
|
||||
async def validate_draft_workspace(
|
||||
self, *, workspace_id: str
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Refresh stored validation status without changing draft revision."""
|
||||
store = self._draft_store()
|
||||
workspace = store.get_workspace(workspace_id)
|
||||
@@ -303,7 +327,9 @@ class WorkflowDraftApi:
|
||||
}
|
||||
)
|
||||
store.save_workspace(refreshed)
|
||||
return get_draft_workspace_record(store, workspace_id=workspace_id)
|
||||
return _PROJECT_DRAFT_WORKSPACE(
|
||||
get_draft_workspace_record(store, workspace_id=workspace_id)
|
||||
)
|
||||
|
||||
async def compile_draft_workspace(self, *, workspace_id: str) -> dict[str, Any]:
|
||||
"""Compile a stored draft workspace without mutating it."""
|
||||
@@ -319,14 +345,16 @@ class WorkflowDraftApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
store = self._draft_store()
|
||||
return patch_draft_workspace_record(
|
||||
store,
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
patch=patch,
|
||||
node_defs_for_draft=self._node_defs_for_draft,
|
||||
return _PROJECT_DRAFT_WORKSPACE(
|
||||
patch_draft_workspace_record(
|
||||
store,
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
patch=patch,
|
||||
node_defs_for_draft=self._node_defs_for_draft,
|
||||
)
|
||||
)
|
||||
|
||||
async def replace_validated_draft_document(
|
||||
@@ -335,13 +363,15 @@ class WorkflowDraftApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Persist a focused, structurally validated edit without provider lookup."""
|
||||
return replace_validated_draft_document(
|
||||
self._draft_store(),
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
draft=draft,
|
||||
return _PROJECT_DRAFT_WORKSPACE(
|
||||
replace_validated_draft_document(
|
||||
self._draft_store(),
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
draft=draft,
|
||||
)
|
||||
)
|
||||
|
||||
async def replace_draft_workspace_document(
|
||||
@@ -350,14 +380,16 @@ class WorkflowDraftApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Replace and semantically revalidate one complete workspace draft."""
|
||||
return replace_draft_workspace_document_record(
|
||||
self._draft_store(),
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
draft=draft,
|
||||
node_defs_for_draft=self._node_defs_for_draft,
|
||||
return _PROJECT_DRAFT_WORKSPACE(
|
||||
replace_draft_workspace_document_record(
|
||||
self._draft_store(),
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
draft=draft,
|
||||
node_defs_for_draft=self._node_defs_for_draft,
|
||||
)
|
||||
)
|
||||
|
||||
async def set_draft_name(
|
||||
@@ -366,7 +398,7 @@ class WorkflowDraftApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
name: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.patch_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -379,7 +411,7 @@ class WorkflowDraftApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Select an entry point, including a forward-referenced step id."""
|
||||
if not isinstance(step_id, str) or not step_id.strip():
|
||||
raise ValueError("draft start step id must not be blank")
|
||||
@@ -398,7 +430,7 @@ class WorkflowDraftApi:
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
outcomes: Sequence[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Replace supplied top-level contract fields in one draft revision.
|
||||
|
||||
Complete schema replacement is intentional: deep merging JSON Schema
|
||||
@@ -445,7 +477,7 @@ class WorkflowDraftApi:
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
target: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.patch_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -469,7 +501,7 @@ class WorkflowDraftApi:
|
||||
step_id: str,
|
||||
input_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
input_values: dict[str, Any] = {}
|
||||
if merge:
|
||||
workspace = self._workspace_if_revision_matches(
|
||||
@@ -504,7 +536,7 @@ class WorkflowDraftApi:
|
||||
step_id: str,
|
||||
output_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
if merge:
|
||||
workspace = self._workspace_if_revision_matches(
|
||||
workspace_id=workspace_id,
|
||||
@@ -539,7 +571,7 @@ class WorkflowDraftApi:
|
||||
revision: int,
|
||||
output_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
output_bindings: list[dict[str, Any]]
|
||||
workspace: WorkflowDraftWorkspace | None = None
|
||||
if merge:
|
||||
|
||||
@@ -34,6 +34,13 @@ from .deployments import (
|
||||
ValidateDeploymentResult,
|
||||
WorkflowDeploymentPayload,
|
||||
)
|
||||
from .drafts import (
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftDiagnosticPayload,
|
||||
DraftWorkspaceResult,
|
||||
DraftWorkspaceSummary,
|
||||
ListDraftWorkspacesResult,
|
||||
)
|
||||
from .runs import (
|
||||
InterruptPayload,
|
||||
InterruptRoutePayload,
|
||||
@@ -55,8 +62,12 @@ __all__ = [
|
||||
"CapabilityRefPayload",
|
||||
"DeleteArtifactResult",
|
||||
"DeleteDeploymentResult",
|
||||
"DeleteDraftWorkspaceResult",
|
||||
"DependencyDiagnosticPayload",
|
||||
"DeploymentSummary",
|
||||
"DraftDiagnosticPayload",
|
||||
"DraftWorkspaceResult",
|
||||
"DraftWorkspaceSummary",
|
||||
"HealthResult",
|
||||
"GuidedResultPayload",
|
||||
"InterruptPayload",
|
||||
@@ -65,6 +76,7 @@ __all__ = [
|
||||
"JsonProjector",
|
||||
"JsonSchema",
|
||||
"ListDeploymentsResult",
|
||||
"ListDraftWorkspacesResult",
|
||||
"ListArtifactsResult",
|
||||
"ListRunsResult",
|
||||
"NextActionPatchExamplePayload",
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""Canonical result models for persisted draft-workspace operations."""
|
||||
|
||||
from typing import Any, Literal, NotRequired, TypedDict
|
||||
|
||||
from .common import JsonObject
|
||||
|
||||
|
||||
class DraftDiagnosticPayload(TypedDict):
|
||||
"""One validation or revision diagnostic attached to a draft workspace."""
|
||||
|
||||
code: str
|
||||
path: str
|
||||
message: str
|
||||
step_id: NotRequired[str | None]
|
||||
repair_hint: NotRequired[str | None]
|
||||
details: NotRequired[JsonObject]
|
||||
|
||||
|
||||
class DraftWorkspaceSummary(TypedDict):
|
||||
"""Compact graph facts included with every persisted workspace result."""
|
||||
|
||||
# Invalid workspaces preserve malformed authoring values for later repair.
|
||||
name: Any
|
||||
start: Any
|
||||
step_count: int
|
||||
route_count: int
|
||||
steps: list[str]
|
||||
|
||||
|
||||
class DraftWorkspaceResult(TypedDict):
|
||||
"""Canonical persisted workspace envelope returned after reads and edits."""
|
||||
|
||||
workspace_id: str
|
||||
revision: int
|
||||
title: str | None
|
||||
status: Literal["valid", "invalid", "conflict"]
|
||||
diagnostics: list[DraftDiagnosticPayload]
|
||||
summary: DraftWorkspaceSummary
|
||||
draft: NotRequired[JsonObject]
|
||||
|
||||
|
||||
class ListDraftWorkspacesResult(TypedDict):
|
||||
"""All persisted draft-workspace summaries."""
|
||||
|
||||
workspaces: list[DraftWorkspaceResult]
|
||||
|
||||
|
||||
class DeleteDraftWorkspaceResult(TypedDict):
|
||||
"""Outcome of deleting one persisted draft workspace."""
|
||||
|
||||
workspace_id: str
|
||||
deleted: bool
|
||||
status: Literal["deleted", "not_found"]
|
||||
+31
-28
@@ -16,8 +16,11 @@ from .drafts import WorkflowDraftApi
|
||||
from .models import (
|
||||
DeleteArtifactResult,
|
||||
DeleteDeploymentResult,
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
ListArtifactsResult,
|
||||
ListDeploymentsResult,
|
||||
ListDraftWorkspacesResult,
|
||||
ListRunsResult,
|
||||
RawWorkflowPlan,
|
||||
RunResult,
|
||||
@@ -263,7 +266,7 @@ class WorkflowApi:
|
||||
|
||||
# -- draft workspaces --
|
||||
|
||||
async def list_draft_workspaces(self) -> dict[str, Any]:
|
||||
async def list_draft_workspaces(self) -> ListDraftWorkspacesResult:
|
||||
return await self.drafts.list_draft_workspaces()
|
||||
|
||||
async def create_draft_workspace(
|
||||
@@ -272,7 +275,7 @@ class WorkflowApi:
|
||||
workspace_id: str,
|
||||
draft: dict[str, Any],
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.create_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
draft=draft,
|
||||
@@ -289,7 +292,7 @@ class WorkflowApi:
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
outcomes: Sequence[str] = ("ok",),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.create_empty_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
name=name,
|
||||
@@ -305,7 +308,7 @@ class WorkflowApi:
|
||||
*,
|
||||
workspace_id: str,
|
||||
include_draft: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.get_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
include_draft=include_draft,
|
||||
@@ -315,14 +318,14 @@ class WorkflowApi:
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DeleteDraftWorkspaceResult:
|
||||
return await self.drafts.delete_draft_workspace(workspace_id=workspace_id)
|
||||
|
||||
async def validate_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.validate_draft_workspace(workspace_id=workspace_id)
|
||||
|
||||
async def compile_draft_workspace(
|
||||
@@ -338,7 +341,7 @@ class WorkflowApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.patch_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -351,7 +354,7 @@ class WorkflowApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Replace and semantically revalidate one complete workspace draft."""
|
||||
return await self.drafts.replace_draft_workspace_document(
|
||||
workspace_id=workspace_id,
|
||||
@@ -365,7 +368,7 @@ class WorkflowApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
name: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.set_draft_name(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -378,7 +381,7 @@ class WorkflowApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.set_draft_start(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -394,7 +397,7 @@ class WorkflowApi:
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
outcomes: Sequence[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.set_draft_contract(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -412,7 +415,7 @@ class WorkflowApi:
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
target: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.set_draft_route(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -429,7 +432,7 @@ class WorkflowApi:
|
||||
step_id: str,
|
||||
input_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.set_step_input_map(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -445,7 +448,7 @@ class WorkflowApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
bindings: Sequence[InputBinding],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.set_step_input_bindings(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -460,7 +463,7 @@ class WorkflowApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
bindings: Sequence[OutputBinding],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.set_step_output_bindings(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -475,7 +478,7 @@ class WorkflowApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
update: CapabilityStepUpdate,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Return the updated workspace summary or a revision-conflict payload."""
|
||||
return await self.draft_authoring.update_capability_step(
|
||||
workspace_id=workspace_id,
|
||||
@@ -492,7 +495,7 @@ class WorkflowApi:
|
||||
step_id: str,
|
||||
output_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.set_step_output_map(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -508,7 +511,7 @@ class WorkflowApi:
|
||||
revision: int,
|
||||
output_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.drafts.set_workflow_output_map(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -522,7 +525,7 @@ class WorkflowApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
bindings: Sequence[InputBinding],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.set_workflow_output_bindings(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -537,7 +540,7 @@ class WorkflowApi:
|
||||
step_id: str,
|
||||
source_path: str,
|
||||
target_path: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.bind_draft(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -562,7 +565,7 @@ class WorkflowApi:
|
||||
desc: str | None = None,
|
||||
retry: int | None = None,
|
||||
timeout_seconds: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.add_step_from_capability(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -588,7 +591,7 @@ class WorkflowApi:
|
||||
step: DraftStep,
|
||||
incoming: RouteSource | None = None,
|
||||
routes: dict[str, str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.add_step(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -605,7 +608,7 @@ class WorkflowApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
routes: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.branch_draft(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -620,7 +623,7 @@ class WorkflowApi:
|
||||
revision: int,
|
||||
branches: list[dict[str, str]],
|
||||
target: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
refs = [
|
||||
RouteSource(step_id=b["step_id"], outcome=b["outcome"]) for b in branches
|
||||
]
|
||||
@@ -646,7 +649,7 @@ class WorkflowApi:
|
||||
output_map: dict[str, str] | None = None,
|
||||
error_message_source: Any | None = None,
|
||||
title: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.create_minimal_draft_workspace(
|
||||
workspace_id=workspace_id,
|
||||
name=name,
|
||||
@@ -669,7 +672,7 @@ class WorkflowApi:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.remove_draft_route(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -683,7 +686,7 @@ class WorkflowApi:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.remove_draft_step(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
@@ -698,7 +701,7 @@ class WorkflowApi:
|
||||
step_id: str,
|
||||
inputs: Sequence[str] = (),
|
||||
outputs: Sequence[str] = (),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
return await self.draft_authoring.remove_draft_binding(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
|
||||
+29
-26
@@ -12,8 +12,11 @@ from .draft_updates import CapabilityStepUpdate
|
||||
from .models import (
|
||||
DeleteArtifactResult,
|
||||
DeleteDeploymentResult,
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
ListArtifactsResult,
|
||||
ListDeploymentsResult,
|
||||
ListDraftWorkspacesResult,
|
||||
ListRunsResult,
|
||||
RunResult,
|
||||
RunTraceResult,
|
||||
@@ -60,14 +63,14 @@ class WorkflowDraftSurface(Protocol):
|
||||
not every same-process authoring helper on ``WorkflowApi``.
|
||||
"""
|
||||
|
||||
async def list_draft_workspaces(self) -> dict[str, Any]: ...
|
||||
async def list_draft_workspaces(self) -> ListDraftWorkspacesResult: ...
|
||||
|
||||
async def get_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
include_draft: bool = False,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def create_draft_workspace_from_capability(
|
||||
self,
|
||||
@@ -96,7 +99,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
outcomes: Sequence[str] = ("ok",),
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def patch_draft_workspace(
|
||||
self,
|
||||
@@ -104,7 +107,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def replace_draft_workspace_document(
|
||||
self,
|
||||
@@ -112,7 +115,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Replace and semantically revalidate one complete workspace draft."""
|
||||
...
|
||||
|
||||
@@ -122,7 +125,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
name: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_draft_start(
|
||||
self,
|
||||
@@ -130,7 +133,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_draft_contract(
|
||||
self,
|
||||
@@ -141,7 +144,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
outcomes: Sequence[str] | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_draft_route(
|
||||
self,
|
||||
@@ -151,7 +154,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
target: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_step_input_map(
|
||||
self,
|
||||
@@ -161,7 +164,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
step_id: str,
|
||||
input_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_step_input_bindings(
|
||||
self,
|
||||
@@ -170,7 +173,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
revision: int,
|
||||
step_id: str,
|
||||
bindings: Sequence[InputBinding],
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_step_output_bindings(
|
||||
self,
|
||||
@@ -179,7 +182,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
revision: int,
|
||||
step_id: str,
|
||||
bindings: Sequence[OutputBinding],
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def update_capability_step(
|
||||
self,
|
||||
@@ -188,7 +191,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
revision: int,
|
||||
step_id: str,
|
||||
update: CapabilityStepUpdate,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_step_output_map(
|
||||
self,
|
||||
@@ -198,7 +201,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
step_id: str,
|
||||
output_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_workflow_output_map(
|
||||
self,
|
||||
@@ -207,7 +210,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
revision: int,
|
||||
output_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def set_workflow_output_bindings(
|
||||
self,
|
||||
@@ -215,7 +218,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
bindings: Sequence[InputBinding],
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def bind_draft(
|
||||
self,
|
||||
@@ -225,7 +228,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
step_id: str,
|
||||
source_path: str,
|
||||
target_path: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def add_step_from_capability(
|
||||
self,
|
||||
@@ -243,7 +246,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
desc: str | None = None,
|
||||
retry: int | None = None,
|
||||
timeout_seconds: int | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def add_step(
|
||||
self,
|
||||
@@ -254,7 +257,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
step: DraftStep,
|
||||
incoming: RouteSource | None = None,
|
||||
routes: dict[str, str] | None = None,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def branch_draft(
|
||||
self,
|
||||
@@ -263,7 +266,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
revision: int,
|
||||
step_id: str,
|
||||
routes: dict[str, str],
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def handle_draft(
|
||||
self,
|
||||
@@ -272,7 +275,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
revision: int,
|
||||
branches: list[dict[str, str]],
|
||||
target: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def remove_draft_route(
|
||||
self,
|
||||
@@ -281,7 +284,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
revision: int,
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def remove_draft_step(
|
||||
self,
|
||||
@@ -289,7 +292,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def remove_draft_binding(
|
||||
self,
|
||||
@@ -299,13 +302,13 @@ class WorkflowDraftSurface(Protocol):
|
||||
step_id: str,
|
||||
inputs: Sequence[str] = (),
|
||||
outputs: Sequence[str] = (),
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def validate_draft_workspace(
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DraftWorkspaceResult: ...
|
||||
|
||||
async def compile_draft_workspace(
|
||||
self,
|
||||
@@ -317,7 +320,7 @@ class WorkflowDraftSurface(Protocol):
|
||||
self,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> DeleteDraftWorkspaceResult: ...
|
||||
|
||||
async def create_artifact_from_workspace(
|
||||
self,
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import Any, Literal
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
from wf_api import CapabilityStepUpdate
|
||||
from wf_api.models import (
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
ListDraftWorkspacesResult,
|
||||
)
|
||||
from wf_api.surface import RouteSource
|
||||
from wf_artifacts.drafts.models import DraftStep
|
||||
from wf_core.models.steps import InputBinding, OutputBinding
|
||||
@@ -11,20 +16,33 @@ from wf_core.models.steps import InputBinding, OutputBinding
|
||||
from .base import RpcCaller
|
||||
|
||||
|
||||
async def _call_draft_workspace(
|
||||
caller: RpcCaller,
|
||||
method: str,
|
||||
params: dict[str, Any],
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Call one server-validated draft method with its canonical client type."""
|
||||
return cast(DraftWorkspaceResult, await caller._call(method, params))
|
||||
|
||||
|
||||
class RpcDraftClientMixin:
|
||||
"""JSON-RPC implementation of workflow draft workspace surface methods."""
|
||||
|
||||
async def list_draft_workspaces(self: RpcCaller) -> dict[str, Any]:
|
||||
return await self._call("workflow.draft_workspaces.list", {})
|
||||
async def list_draft_workspaces(self: RpcCaller) -> ListDraftWorkspacesResult:
|
||||
return cast(
|
||||
ListDraftWorkspacesResult,
|
||||
await self._call("workflow.draft_workspaces.list", {}),
|
||||
)
|
||||
|
||||
async def get_draft_workspace(
|
||||
self: RpcCaller,
|
||||
*,
|
||||
workspace_id: str,
|
||||
include_draft: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
"""Return the remote workspace summary or revision-conflict payload."""
|
||||
return await self._call(
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.get",
|
||||
{"workspace_id": workspace_id, "include_draft": include_draft},
|
||||
)
|
||||
@@ -73,8 +91,9 @@ class RpcDraftClientMixin:
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
outcomes: Sequence[str] = ("ok",),
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.create_empty",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -93,8 +112,9 @@ class RpcDraftClientMixin:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
patch: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.patch",
|
||||
{"workspace_id": workspace_id, "revision": revision, "patch": patch},
|
||||
)
|
||||
@@ -105,8 +125,9 @@ class RpcDraftClientMixin:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
draft: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.replace_document",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -121,8 +142,9 @@ class RpcDraftClientMixin:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
name: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_name",
|
||||
{"workspace_id": workspace_id, "revision": revision, "name": name},
|
||||
)
|
||||
@@ -133,8 +155,9 @@ class RpcDraftClientMixin:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_start",
|
||||
{"workspace_id": workspace_id, "revision": revision, "step_id": step_id},
|
||||
)
|
||||
@@ -148,8 +171,9 @@ class RpcDraftClientMixin:
|
||||
state_schema: dict[str, Any] | None = None,
|
||||
output_schema: dict[str, Any] | None = None,
|
||||
outcomes: Sequence[str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_contract",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -169,8 +193,9 @@ class RpcDraftClientMixin:
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
target: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_route",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -189,8 +214,9 @@ class RpcDraftClientMixin:
|
||||
step_id: str,
|
||||
input_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_step_input_map",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -208,8 +234,9 @@ class RpcDraftClientMixin:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
bindings: Sequence[InputBinding],
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_step_input_bindings",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -226,8 +253,9 @@ class RpcDraftClientMixin:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
bindings: Sequence[OutputBinding],
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_step_output_bindings",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -244,8 +272,9 @@ class RpcDraftClientMixin:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
update: CapabilityStepUpdate,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.update_capability_step",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -263,8 +292,9 @@ class RpcDraftClientMixin:
|
||||
step_id: str,
|
||||
output_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_step_output_map",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -282,8 +312,9 @@ class RpcDraftClientMixin:
|
||||
revision: int,
|
||||
output_map: dict[str, str],
|
||||
merge: bool = False,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_workflow_output_map",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -299,8 +330,9 @@ class RpcDraftClientMixin:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
bindings: Sequence[InputBinding],
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.set_workflow_output_bindings",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -317,8 +349,9 @@ class RpcDraftClientMixin:
|
||||
step_id: str,
|
||||
source_path: str,
|
||||
target_path: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.bind",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -345,7 +378,7 @@ class RpcDraftClientMixin:
|
||||
desc: str | None = None,
|
||||
retry: int | None = None,
|
||||
timeout_seconds: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
if input_map is not None and input_bindings is not None:
|
||||
raise ValueError("input_map and input_bindings are mutually exclusive")
|
||||
params: dict[str, object] = {
|
||||
@@ -370,7 +403,8 @@ class RpcDraftClientMixin:
|
||||
params["retry"] = retry
|
||||
if timeout_seconds is not None:
|
||||
params["timeout_seconds"] = timeout_seconds
|
||||
return await self._call(
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.add_step_from_capability",
|
||||
params,
|
||||
)
|
||||
@@ -384,8 +418,9 @@ class RpcDraftClientMixin:
|
||||
step: DraftStep,
|
||||
incoming: RouteSource | None = None,
|
||||
routes: dict[str, str] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.add_step",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -408,8 +443,9 @@ class RpcDraftClientMixin:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
routes: dict[str, str],
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.branch",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -426,8 +462,9 @@ class RpcDraftClientMixin:
|
||||
revision: int,
|
||||
branches: list[dict[str, str]],
|
||||
target: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.handle",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -444,8 +481,9 @@ class RpcDraftClientMixin:
|
||||
revision: int,
|
||||
step_id: str,
|
||||
outcome: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.remove_route",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -461,8 +499,9 @@ class RpcDraftClientMixin:
|
||||
workspace_id: str,
|
||||
revision: int,
|
||||
step_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.remove_step",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -479,8 +518,9 @@ class RpcDraftClientMixin:
|
||||
step_id: str,
|
||||
inputs: Sequence[str] = (),
|
||||
outputs: Sequence[str] = (),
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.remove_binding",
|
||||
{
|
||||
"workspace_id": workspace_id,
|
||||
@@ -495,8 +535,9 @@ class RpcDraftClientMixin:
|
||||
self: RpcCaller,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
) -> DraftWorkspaceResult:
|
||||
return await _call_draft_workspace(
|
||||
self,
|
||||
"workflow.draft_workspaces.validate",
|
||||
{"workspace_id": workspace_id},
|
||||
)
|
||||
@@ -515,10 +556,13 @@ class RpcDraftClientMixin:
|
||||
self: RpcCaller,
|
||||
*,
|
||||
workspace_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.draft_workspaces.delete",
|
||||
{"workspace_id": workspace_id},
|
||||
) -> DeleteDraftWorkspaceResult:
|
||||
return cast(
|
||||
DeleteDraftWorkspaceResult,
|
||||
await self._call(
|
||||
"workflow.draft_workspaces.delete",
|
||||
{"workspace_id": workspace_id},
|
||||
),
|
||||
)
|
||||
|
||||
async def create_artifact_from_workspace(
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
from __future__ import annotations
|
||||
"""Draft JSON-RPC method registration.
|
||||
|
||||
Return annotations stay eagerly evaluated because fastapi-jsonrpc captures them
|
||||
while registering nested handlers for response validation and OpenRPC output.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import fastapi_jsonrpc as jsonrpc
|
||||
|
||||
from wf_api.models import (
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
ListDraftWorkspacesResult,
|
||||
)
|
||||
from wf_api.surface import RouteSource
|
||||
from wf_server import WorkflowServer
|
||||
|
||||
@@ -83,7 +92,7 @@ def register_methods(
|
||||
@entrypoint.method(name="workflow.draft_workspaces.list", errors=[WorkflowRpcError])
|
||||
async def workflow_draft_workspaces_list(
|
||||
params: ListDraftWorkspacesParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> ListDraftWorkspacesResult:
|
||||
try:
|
||||
return await server.api.list_draft_workspaces()
|
||||
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
|
||||
@@ -92,7 +101,7 @@ def register_methods(
|
||||
@entrypoint.method(name="workflow.draft_workspaces.get", errors=[WorkflowRpcError])
|
||||
async def workflow_draft_workspaces_get(
|
||||
params: GetDraftWorkspaceParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.get_draft_workspace(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -119,7 +128,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_create_empty(
|
||||
params: CreateEmptyDraftWorkspaceParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.create_empty_draft_workspace(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -138,7 +147,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_patch(
|
||||
params: PatchDraftWorkspaceParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.patch_draft_workspace(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -154,7 +163,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_replace_document(
|
||||
params: ReplaceDraftWorkspaceDocumentParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.replace_draft_workspace_document(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -169,7 +178,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_name(
|
||||
params: SetDraftNameParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_draft_name(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -184,7 +193,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_start(
|
||||
params: SetDraftStartParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_draft_start(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -199,7 +208,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_contract(
|
||||
params: SetDraftContractParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_draft_contract(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -217,7 +226,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_route(
|
||||
params: SetDraftRouteParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_draft_route(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -235,7 +244,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_step_input_map(
|
||||
params: SetStepInputMapParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_step_input_map(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -253,7 +262,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_step_input_bindings(
|
||||
params: SetStepInputBindingsParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_step_input_bindings(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -270,7 +279,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_update_capability_step(
|
||||
params: UpdateCapabilityStepParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.update_capability_step(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -287,7 +296,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_step_output_bindings(
|
||||
params: SetStepOutputBindingsParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_step_output_bindings(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -304,7 +313,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_step_output_map(
|
||||
params: SetStepOutputMapParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_step_output_map(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -322,7 +331,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_workflow_output_map(
|
||||
params: SetWorkflowOutputMapParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_workflow_output_map(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -339,7 +348,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_set_workflow_output_bindings(
|
||||
params: SetWorkflowOutputBindingsParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.set_workflow_output_bindings(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -355,7 +364,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_bind(
|
||||
params: BindDraftParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.bind_draft(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -373,7 +382,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_add_step_from_capability(
|
||||
params: AddStepFromCapabilityParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.add_step_from_capability(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -399,7 +408,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_add_step(
|
||||
params: AddDraftStepParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
incoming = (
|
||||
None
|
||||
@@ -425,7 +434,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_branch(
|
||||
params: BranchDraftParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.branch_draft(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -441,7 +450,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_handle(
|
||||
params: HandleDraftParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.handle_draft(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -460,7 +469,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_validate(
|
||||
params: ValidateDraftWorkspaceParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.validate_draft_workspace(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -486,7 +495,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_delete(
|
||||
params: DeleteDraftWorkspaceParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DeleteDraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.delete_draft_workspace(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -499,7 +508,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_remove_route(
|
||||
params: RemoveDraftRouteParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.remove_draft_route(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -515,7 +524,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_remove_step(
|
||||
params: RemoveDraftStepParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.remove_draft_step(
|
||||
workspace_id=params.workspace_id,
|
||||
@@ -530,7 +539,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_draft_workspaces_remove_binding(
|
||||
params: RemoveDraftBindingParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> DraftWorkspaceResult:
|
||||
try:
|
||||
return await server.api.remove_draft_binding(
|
||||
workspace_id=params.workspace_id,
|
||||
|
||||
@@ -760,6 +760,28 @@ async def test_create_draft_workspace_creates_workspace(tmp_path: Path) -> None:
|
||||
assert fetched["draft"]["steps"]["echo"]["use"] == "demo.personal.echo_tool"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_summary_values_remain_inspectable_and_listable(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "invalid_summary_values")
|
||||
api, _service, _authoring = _draft_api(artifact_store)
|
||||
draft = _echo_draft()
|
||||
draft["name"] = 123
|
||||
draft["start"] = ["echo"]
|
||||
|
||||
created = await api.create_draft_workspace(
|
||||
workspace_id="repairable",
|
||||
draft=draft,
|
||||
)
|
||||
listed = await api.list_draft_workspaces()
|
||||
|
||||
assert created["status"] == "invalid"
|
||||
assert created["summary"]["name"] == 123
|
||||
assert created["summary"]["start"] == ["echo"]
|
||||
assert listed["workspaces"][0]["workspace_id"] == "repairable"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_empty_draft_workspace_persists_invalid_skeleton(
|
||||
tmp_path: Path,
|
||||
|
||||
@@ -99,6 +99,109 @@ def test_openrpc_exposes_typed_artifact_results(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"method_name",
|
||||
[
|
||||
"workflow.draft_workspaces.get",
|
||||
"workflow.draft_workspaces.create_empty",
|
||||
"workflow.draft_workspaces.patch",
|
||||
"workflow.draft_workspaces.replace_document",
|
||||
"workflow.draft_workspaces.set_name",
|
||||
"workflow.draft_workspaces.set_start",
|
||||
"workflow.draft_workspaces.set_contract",
|
||||
"workflow.draft_workspaces.set_route",
|
||||
"workflow.draft_workspaces.set_step_input_map",
|
||||
"workflow.draft_workspaces.set_step_input_bindings",
|
||||
"workflow.draft_workspaces.update_capability_step",
|
||||
"workflow.draft_workspaces.set_step_output_bindings",
|
||||
"workflow.draft_workspaces.set_step_output_map",
|
||||
"workflow.draft_workspaces.set_workflow_output_map",
|
||||
"workflow.draft_workspaces.set_workflow_output_bindings",
|
||||
"workflow.draft_workspaces.bind",
|
||||
"workflow.draft_workspaces.add_step_from_capability",
|
||||
"workflow.draft_workspaces.add_step",
|
||||
"workflow.draft_workspaces.branch",
|
||||
"workflow.draft_workspaces.handle",
|
||||
"workflow.draft_workspaces.validate",
|
||||
"workflow.draft_workspaces.remove_route",
|
||||
"workflow.draft_workspaces.remove_step",
|
||||
"workflow.draft_workspaces.remove_binding",
|
||||
],
|
||||
)
|
||||
def test_openrpc_exposes_typed_draft_workspace_results(
|
||||
openrpc_document: dict[str, Any],
|
||||
method_name: str,
|
||||
) -> None:
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name=method_name,
|
||||
component_name="DraftWorkspaceResult",
|
||||
properties={
|
||||
"workspace_id",
|
||||
"revision",
|
||||
"title",
|
||||
"status",
|
||||
"diagnostics",
|
||||
"summary",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_draft_workspace_list_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name="workflow.draft_workspaces.list",
|
||||
component_name="ListDraftWorkspacesResult",
|
||||
properties={"workspaces"},
|
||||
)
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_delete_draft_workspace_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name="workflow.draft_workspaces.delete",
|
||||
component_name="DeleteDraftWorkspaceResult",
|
||||
properties={"workspace_id", "deleted", "status"},
|
||||
)
|
||||
|
||||
|
||||
def test_openrpc_pins_nested_draft_workspace_contract(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
schemas = openrpc_document["components"]["schemas"]
|
||||
workspace = schemas["DraftWorkspaceResult"]
|
||||
diagnostic = schemas["DraftDiagnosticPayload"]
|
||||
listed = schemas["ListDraftWorkspacesResult"]
|
||||
deleted = schemas["DeleteDraftWorkspaceResult"]
|
||||
|
||||
assert workspace["required"] == [
|
||||
"workspace_id",
|
||||
"revision",
|
||||
"title",
|
||||
"status",
|
||||
"diagnostics",
|
||||
"summary",
|
||||
]
|
||||
assert "draft" not in workspace["required"]
|
||||
assert workspace["properties"]["status"]["enum"] == [
|
||||
"valid",
|
||||
"invalid",
|
||||
"conflict",
|
||||
]
|
||||
assert workspace["properties"]["diagnostics"]["items"] == {
|
||||
"$ref": "#/components/schemas/DraftDiagnosticPayload"
|
||||
}
|
||||
assert diagnostic["required"] == ["code", "path", "message"]
|
||||
assert listed["properties"]["workspaces"]["items"] == {
|
||||
"$ref": "#/components/schemas/DraftWorkspaceResult"
|
||||
}
|
||||
assert deleted["properties"]["status"]["enum"] == ["deleted", "not_found"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method_name", "component_name", "properties"),
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user