feat: type draft workspace results

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