feat: type exceptional draft results

This commit is contained in:
lda
2026-07-31 13:01:05 +07:00 Verified
parent e60c391610
commit c96fee4e7d
11 changed files with 391 additions and 122 deletions
+6 -6
View File
@@ -72,12 +72,12 @@
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`, 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.
and run operations, every persisted draft-workspace operation, and the
capability-bootstrap alias named transport-neutral result schemas: 47 of
70 methods. The remaining 23 success results still collapse to generic
objects across capability discovery/call, stateless draft patch/validate,
source discovery, and source-registry/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.
+31 -21
View File
@@ -25,11 +25,14 @@ from .capability_requirements import observed_node_specs
from .drafts import WorkflowDraftApi
from .listing import matches_query, paged_list_payload
from .models import (
CreateArtifactFromWorkspaceResult,
DeleteArtifactResult,
JsonProjector,
ListArtifactsResult,
RawWorkflowPlan,
SaveArtifactResult,
SavedDraftArtifactResult,
UnsavedDraftArtifactResult,
WorkflowArtifactPayload,
)
from .operation_context import WorkflowOperationContext
@@ -38,6 +41,8 @@ _PROJECT_ARTIFACT = JsonProjector(WorkflowArtifactPayload)
_PROJECT_ARTIFACT_LIST = JsonProjector(ListArtifactsResult)
_PROJECT_ARTIFACT_SAVE = JsonProjector(SaveArtifactResult)
_PROJECT_ARTIFACT_DELETE = JsonProjector(DeleteArtifactResult)
_PROJECT_UNSAVED_DRAFT_ARTIFACT = JsonProjector(UnsavedDraftArtifactResult)
_PROJECT_SAVED_DRAFT_ARTIFACT = JsonProjector(SavedDraftArtifactResult)
class WorkflowArtifactApi:
@@ -95,9 +100,7 @@ class WorkflowArtifactApi:
paged_list_payload("nodes", entries, cursor=cursor, limit=limit)
)
async def save_artifact(
self, artifact: dict[str, Any]
) -> SaveArtifactResult:
async def save_artifact(self, artifact: dict[str, Any]) -> SaveArtifactResult:
workflow_artifact = WorkflowArtifact.model_validate(artifact)
self._artifact_store().save_artifact(workflow_artifact)
self.context.events.record_workflow_event(
@@ -182,7 +185,7 @@ class WorkflowArtifactApi:
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
) -> SavedDraftArtifactResult:
from wf_artifacts import compile_workflow_draft
plan = compile_workflow_draft(draft)
@@ -202,6 +205,24 @@ class WorkflowArtifactApi:
observed_node_specs=observed_node_specs(self.context),
created_from_catalog_version=created_from_catalog_version,
)
required_sources = _binding_required_sources(
workflow_artifact.required_capability_map(),
self.context.specs.capability_sources,
)
result = _PROJECT_SAVED_DRAFT_ARTIFACT(
{
"artifact_id": workflow_artifact.id,
"version": workflow_artifact.version,
"saved": True,
"required_logical_sources": required_sources,
"suggested_bindings": _suggested_self_bindings(
required_sources,
self.context.specs.capability_sources,
),
}
)
# Validate the public result before persistence so a projection bug
# cannot report failure after the artifact has already been saved.
self._artifact_store().save_artifact(workflow_artifact)
self.context.events.record_workflow_event(
"workflow_artifact_saved",
@@ -212,20 +233,7 @@ class WorkflowArtifactApi:
"created_from_draft": True,
},
)
required_sources = _binding_required_sources(
workflow_artifact.required_capability_map(),
self.context.specs.capability_sources,
)
return {
"artifact_id": workflow_artifact.id,
"version": workflow_artifact.version,
"saved": True,
"required_logical_sources": required_sources,
"suggested_bindings": _suggested_self_bindings(
required_sources,
self.context.specs.capability_sources,
),
}
return result
async def create_artifact_from_workspace(
self,
@@ -240,20 +248,22 @@ class WorkflowArtifactApi:
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
) -> CreateArtifactFromWorkspaceResult:
store = self.context.draft_workspace_store
if store is None:
raise KeyError("draft workspace store is not configured")
workspace = store.get_workspace(workspace_id)
validation = await self.drafts.validate_draft(draft=workspace.draft)
if validation["status"] != "valid":
return {
return _PROJECT_UNSAVED_DRAFT_ARTIFACT(
{
"saved": False,
"workspace_id": workspace_id,
"revision": workspace.revision,
"status": validation["status"],
"diagnostics": validation["diagnostics"],
}
)
return await self.create_artifact_from_draft(
artifact_id=artifact_id,
version=version,
@@ -279,7 +289,7 @@ class WorkflowArtifactApi:
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
) -> CreateArtifactFromWorkspaceResult:
"""Save the current draft workspace as a callable wrapper artifact."""
return await self.create_artifact_from_workspace(
workspace_id=workspace_id,
+28 -9
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
from collections.abc import Mapping, Sequence
from typing import Any
from typing import Any, cast
from wf_artifacts import (
DependencyDiagnostic,
@@ -21,6 +21,12 @@ from .capability_requirements import required_capability_payloads
from .draft_authoring import WorkflowDraftAuthoringApi
from .drafts import WorkflowDraftApi
from .listing import matches_query, paged_list_payload
from .models import (
CreateDraftWorkspaceFromCapabilityResult,
JsonProjector,
NextActionsPayload,
WrapperAuthoringHintsPayload,
)
from .next_actions import NextActions
from .operation_context import WorkflowOperationContext
from .refs import parse_workflow_surface_capability_id
@@ -30,6 +36,8 @@ from .wrapper_hints import (
wrapper_hints_for_capability,
)
_PROJECT_WRAPPER_HINTS = JsonProjector(WrapperAuthoringHintsPayload)
def _schema_field_names(schema: dict[str, Any]) -> list[str]:
"""Return top-level JSON object property names for compact discovery rows."""
@@ -351,10 +359,12 @@ class WorkflowCapabilityApi:
input_map: dict[str, str] | None = None,
output_map: dict[str, str] | None = None,
error_message_source: str | GraphSourcePath | None = None,
) -> dict[str, Any]:
) -> CreateDraftWorkspaceFromCapabilityResult:
"""Create a patchable draft workspace from inspect_capability hints."""
capability = await self.inspect_capability(qualified_name=capability_name)
hints = capability["wrapper_hints"]
# Validate capability-derived guidance before workspace creation. The
# workspace result is already projected by the draft-workspace API.
hints = _PROJECT_WRAPPER_HINTS(capability["wrapper_hints"])
result = await self.draft_authoring.create_minimal_draft_workspace(
workspace_id=workspace_id,
name=name or _draft_name_from_capability(capability_name),
@@ -371,12 +381,21 @@ class WorkflowCapabilityApi:
error_message_source=error_message_source,
title=title,
)
return {
**result,
"wrapper_hints": hints,
"next_actions": NextActions.from_wrapper_hints(
next_actions = cast(
NextActionsPayload,
NextActions.from_wrapper_hints(
workspace_id=workspace_id,
revision=int(result["revision"]),
hints=hints,
hints=dict(hints),
).model_dump(mode="json"),
}
)
# Both merged extensions are independently validated or model-dumped,
# while the workspace payload was projected by the mutating API.
return cast(
CreateDraftWorkspaceFromCapabilityResult,
{
**result,
"wrapper_hints": hints,
"next_actions": next_actions,
},
)
+15 -4
View File
@@ -53,8 +53,11 @@ from .draft_payloads import (
output_bindings_payload as _draft_output_bindings_payload,
)
from .models import (
CompileDraftWorkspaceResult,
CompileDraftWorkspaceSuccess,
DeleteDraftWorkspaceResult,
DraftWorkspaceResult,
InvalidDraftResult,
JsonProjector,
ListDraftWorkspacesResult,
)
@@ -64,6 +67,8 @@ from .schema_projection import project_property_to_schema_path, schema_path_exis
_PROJECT_DRAFT_WORKSPACE = JsonProjector(DraftWorkspaceResult)
_PROJECT_DRAFT_WORKSPACE_LIST = JsonProjector(ListDraftWorkspacesResult)
_PROJECT_DRAFT_WORKSPACE_DELETE = JsonProjector(DeleteDraftWorkspaceResult)
_PROJECT_DRAFT_COMPILE = JsonProjector(CompileDraftWorkspaceSuccess)
_PROJECT_INVALID_DRAFT = JsonProjector(InvalidDraftResult)
def _empty_object_schema() -> dict[str, Any]:
@@ -176,9 +181,12 @@ class WorkflowDraftApi:
node_defs=self._node_defs_for_draft(draft),
)
async def compile_draft(self, *, draft: dict[str, Any]) -> dict[str, Any]:
async def compile_draft(
self, *, draft: dict[str, Any]
) -> CompileDraftWorkspaceSuccess:
plan = compile_workflow_draft(draft)
return {
return _PROJECT_DRAFT_COMPILE(
{
"compiled_plan": plan,
"required_capabilities": required_capability_payloads(
required_capabilities_for_plan(
@@ -188,6 +196,7 @@ class WorkflowDraftApi:
)
),
}
)
async def patch_draft(
self,
@@ -331,12 +340,14 @@ class WorkflowDraftApi:
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
) -> CompileDraftWorkspaceResult:
"""Compile a stored draft workspace without mutating it."""
workspace = self._draft_store().get_workspace(workspace_id)
validation = await self.validate_draft(draft=workspace.draft)
if validation["status"] != "valid":
return validation
return _PROJECT_INVALID_DRAFT(validation)
return await self.compile_draft(draft=workspace.draft)
async def patch_draft_workspace(
+20
View File
@@ -35,11 +35,21 @@ from .deployments import (
WorkflowDeploymentPayload,
)
from .drafts import (
CompileDraftWorkspaceResult,
CompileDraftWorkspaceSuccess,
CreateArtifactFromWorkspaceResult,
CreateDraftWorkspaceFromCapabilityResult,
DeleteDraftWorkspaceResult,
DraftDiagnosticPayload,
DraftWorkspaceResult,
DraftWorkspaceSummary,
InvalidDraftResult,
ListDraftWorkspacesResult,
SavedDraftArtifactResult,
UnsavedDraftArtifactResult,
WrapperAuthoringHintsPayload,
WrapperMissingDecisionPayload,
WrapperOutcomeCandidatePayload,
)
from .runs import (
InterruptPayload,
@@ -60,6 +70,10 @@ __all__ = [
"ArtifactKindPayload",
"CapabilityKindPayload",
"CapabilityRefPayload",
"CompileDraftWorkspaceResult",
"CompileDraftWorkspaceSuccess",
"CreateArtifactFromWorkspaceResult",
"CreateDraftWorkspaceFromCapabilityResult",
"DeleteArtifactResult",
"DeleteDeploymentResult",
"DeleteDraftWorkspaceResult",
@@ -72,6 +86,7 @@ __all__ = [
"GuidedResultPayload",
"InterruptPayload",
"InterruptRoutePayload",
"InvalidDraftResult",
"JsonObject",
"JsonProjector",
"JsonSchema",
@@ -90,12 +105,17 @@ __all__ = [
"RunTraceResult",
"RequiredCapabilityPayload",
"SaveArtifactResult",
"SavedDraftArtifactResult",
"SaveDeploymentResult",
"SourceBindingPayload",
"TraceRange",
"TraceEntryPayload",
"UnsavedDraftArtifactResult",
"ValidateDeploymentResult",
"WorkflowDeploymentPayload",
"WorkflowArtifactPayload",
"WorkflowRefPayload",
"WrapperAuthoringHintsPayload",
"WrapperMissingDecisionPayload",
"WrapperOutcomeCandidatePayload",
]
+95 -1
View File
@@ -2,7 +2,8 @@
from typing import Any, Literal, NotRequired, TypedDict
from .common import JsonObject
from .artifacts import RequiredCapabilityPayload
from .common import JsonObject, NextActionsPayload
class DraftDiagnosticPayload(TypedDict):
@@ -51,3 +52,96 @@ class DeleteDraftWorkspaceResult(TypedDict):
workspace_id: str
deleted: bool
status: Literal["deleted", "not_found"]
class InvalidDraftResult(TypedDict):
"""Validation-only result returned when a draft cannot be compiled."""
status: Literal["invalid"]
diagnostics: list[DraftDiagnosticPayload]
class CompileDraftWorkspaceSuccess(TypedDict):
"""Compiled raw plan and dependencies for one valid draft workspace."""
compiled_plan: JsonObject
required_capabilities: dict[str, RequiredCapabilityPayload]
type CompileDraftWorkspaceResult = CompileDraftWorkspaceSuccess | InvalidDraftResult
class WrapperOutcomeCandidatePayload(TypedDict):
"""Possible wrapper outcome mapping that still requires caller judgment."""
kind: Literal["boolean_control_field"]
source: str
candidate_outcomes: list[str]
confidence: Literal["high", "medium", "low"]
reason: str
automatic: bool
class WrapperMissingDecisionPayload(TypedDict):
"""One unresolved wrapper-authoring decision."""
kind: Literal[
"choose_output_fields",
"review_nested_output",
"confirm_boolean_outcomes",
"choose_error_mapping",
]
message: str
class WrapperAuthoringHintsPayload(TypedDict):
"""Transport-neutral projection of conservative wrapper scaffolding hints."""
capability_name: str
confidence: Literal["high", "medium", "low"]
declared_outcomes: list[str]
suggested_wrapper_outcomes: list[str]
outcome_policy: Literal[
"preserve_declared",
"manual_mapping_required",
]
input_schema: JsonObject
state_schema: JsonObject
output_schema: JsonObject
input_map: dict[str, str]
output_map: dict[str, str]
outcome_candidates: list[WrapperOutcomeCandidatePayload]
missing_decisions: list[WrapperMissingDecisionPayload]
notes: list[str]
class CreateDraftWorkspaceFromCapabilityResult(DraftWorkspaceResult):
"""Bootstrapped workspace plus the hints and next actions that shaped it."""
wrapper_hints: WrapperAuthoringHintsPayload
next_actions: NextActionsPayload
class UnsavedDraftArtifactResult(TypedDict):
"""Validation result when an invalid workspace is not saved as an artifact."""
saved: Literal[False]
workspace_id: str
revision: int
status: Literal["invalid"]
diagnostics: list[DraftDiagnosticPayload]
class SavedDraftArtifactResult(TypedDict):
"""Saved artifact result with source-binding guidance derived from a draft."""
artifact_id: str
version: int
saved: Literal[True]
required_logical_sources: list[str]
suggested_bindings: dict[str, str]
type CreateArtifactFromWorkspaceResult = (
SavedDraftArtifactResult | UnsavedDraftArtifactResult
)
+11 -6
View File
@@ -14,6 +14,10 @@ from .draft_authoring import RouteSource, WorkflowDraftAuthoringApi
from .draft_updates import CapabilityStepUpdate
from .drafts import WorkflowDraftApi
from .models import (
CompileDraftWorkspaceResult,
CompileDraftWorkspaceSuccess,
CreateArtifactFromWorkspaceResult,
CreateDraftWorkspaceFromCapabilityResult,
DeleteArtifactResult,
DeleteDeploymentResult,
DeleteDraftWorkspaceResult,
@@ -26,6 +30,7 @@ from .models import (
RunResult,
RunTraceResult,
SaveArtifactResult,
SavedDraftArtifactResult,
SaveDeploymentResult,
ValidateDeploymentResult,
WorkflowArtifactPayload,
@@ -174,7 +179,7 @@ class WorkflowApi:
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
) -> SavedDraftArtifactResult:
return await self.artifacts.create_artifact_from_draft(
artifact_id=artifact_id,
version=version,
@@ -201,7 +206,7 @@ class WorkflowApi:
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
) -> CreateArtifactFromWorkspaceResult:
return await self.artifacts.create_artifact_from_workspace(
workspace_id=workspace_id,
artifact_id=artifact_id,
@@ -227,7 +232,7 @@ class WorkflowApi:
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
) -> CreateArtifactFromWorkspaceResult:
return await self.artifacts.create_wrapper_from_workspace(
workspace_id=workspace_id,
artifact_id=artifact_id,
@@ -253,7 +258,7 @@ class WorkflowApi:
self,
*,
draft: dict[str, Any],
) -> dict[str, Any]:
) -> CompileDraftWorkspaceSuccess:
return await self.drafts.compile_draft(draft=draft)
async def patch_draft(
@@ -332,7 +337,7 @@ class WorkflowApi:
self,
*,
workspace_id: str,
) -> dict[str, Any]:
) -> CompileDraftWorkspaceResult:
return await self.drafts.compile_draft_workspace(workspace_id=workspace_id)
async def patch_draft_workspace(
@@ -725,7 +730,7 @@ class WorkflowApi:
input_map: dict[str, str] | None = None,
output_map: dict[str, str] | None = None,
error_message_source: Any | None = None,
) -> dict[str, Any]:
) -> CreateDraftWorkspaceFromCapabilityResult:
return await self.capabilities.create_draft_workspace_from_capability(
workspace_id=workspace_id,
capability_name=capability_name,
+7 -4
View File
@@ -10,6 +10,9 @@ from wf_core.models.steps import InputBinding, OutputBinding
from .draft_authoring import RouteSource
from .draft_updates import CapabilityStepUpdate
from .models import (
CompileDraftWorkspaceResult,
CreateArtifactFromWorkspaceResult,
CreateDraftWorkspaceFromCapabilityResult,
DeleteArtifactResult,
DeleteDeploymentResult,
DeleteDraftWorkspaceResult,
@@ -87,7 +90,7 @@ class WorkflowDraftSurface(Protocol):
input_map: dict[str, str] | None = None,
output_map: dict[str, str] | None = None,
error_message_source: Any | None = None,
) -> dict[str, Any]: ...
) -> CreateDraftWorkspaceFromCapabilityResult: ...
async def create_empty_draft_workspace(
self,
@@ -314,7 +317,7 @@ class WorkflowDraftSurface(Protocol):
self,
*,
workspace_id: str,
) -> dict[str, Any]: ...
) -> CompileDraftWorkspaceResult: ...
async def delete_draft_workspace(
self,
@@ -335,7 +338,7 @@ class WorkflowDraftSurface(Protocol):
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]: ...
) -> CreateArtifactFromWorkspaceResult: ...
async def create_wrapper_from_workspace(
self,
@@ -349,7 +352,7 @@ class WorkflowDraftSurface(Protocol):
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]: ...
) -> CreateArtifactFromWorkspaceResult: ...
class WorkflowArtifactSurface(Protocol):
+23 -8
View File
@@ -5,6 +5,9 @@ from typing import Any, Literal, cast
from wf_api import CapabilityStepUpdate
from wf_api.models import (
CompileDraftWorkspaceResult,
CreateArtifactFromWorkspaceResult,
CreateDraftWorkspaceFromCapabilityResult,
DeleteDraftWorkspaceResult,
DraftWorkspaceResult,
ListDraftWorkspacesResult,
@@ -62,8 +65,10 @@ class RpcDraftClientMixin:
input_map: dict[str, str] | None = None,
output_map: dict[str, str] | None = None,
error_message_source: Any | None = None,
) -> dict[str, Any]:
return await self._call(
) -> CreateDraftWorkspaceFromCapabilityResult:
return cast(
CreateDraftWorkspaceFromCapabilityResult,
await self._call(
"workflow.draft_workspaces.create_from_capability",
{
"workspace_id": workspace_id,
@@ -79,6 +84,7 @@ class RpcDraftClientMixin:
"output_map": output_map,
"error_message_source": error_message_source,
},
),
)
async def create_empty_draft_workspace(
@@ -546,10 +552,13 @@ class RpcDraftClientMixin:
self: RpcCaller,
*,
workspace_id: str,
) -> dict[str, Any]:
return await self._call(
) -> CompileDraftWorkspaceResult:
return cast(
CompileDraftWorkspaceResult,
await self._call(
"workflow.draft_workspaces.compile",
{"workspace_id": workspace_id},
),
)
async def delete_draft_workspace(
@@ -578,8 +587,10 @@ class RpcDraftClientMixin:
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
return await self._call(
) -> CreateArtifactFromWorkspaceResult:
return cast(
CreateArtifactFromWorkspaceResult,
await self._call(
"workflow.draft_workspaces.create_artifact",
{
"workspace_id": workspace_id,
@@ -593,6 +604,7 @@ class RpcDraftClientMixin:
"source_bindings": source_bindings,
"created_from_catalog_version": created_from_catalog_version,
},
),
)
async def create_wrapper_from_workspace(
@@ -607,8 +619,10 @@ class RpcDraftClientMixin:
required_capabilities: dict[str, dict[str, Any]] | None = None,
source_bindings: dict[str, str] | None = None,
created_from_catalog_version: str | None = None,
) -> dict[str, Any]:
return await self._call(
) -> CreateArtifactFromWorkspaceResult:
return cast(
CreateArtifactFromWorkspaceResult,
await self._call(
"workflow.draft_workspaces.create_wrapper",
{
"workspace_id": workspace_id,
@@ -621,4 +635,5 @@ class RpcDraftClientMixin:
"source_bindings": source_bindings,
"created_from_catalog_version": created_from_catalog_version,
},
),
)
+9 -6
View File
@@ -9,6 +9,9 @@ from typing import Any
import fastapi_jsonrpc as jsonrpc
from wf_api.models import (
CompileDraftWorkspaceResult,
CreateArtifactFromWorkspaceResult,
CreateDraftWorkspaceFromCapabilityResult,
DeleteDraftWorkspaceResult,
DraftWorkspaceResult,
ListDraftWorkspacesResult,
@@ -65,7 +68,7 @@ def register_methods(
)
async def workflow_drafts_create_from_capability(
params: CreateDraftFromCapabilityParams = RpcParams(),
) -> dict[str, Any]:
) -> CreateDraftWorkspaceFromCapabilityResult:
try:
return await _create_from_capability(server, params)
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
@@ -116,7 +119,7 @@ def register_methods(
)
async def workflow_draft_workspaces_create_from_capability(
params: CreateDraftFromCapabilityParams = RpcParams(),
) -> dict[str, Any]:
) -> CreateDraftWorkspaceFromCapabilityResult:
try:
return await _create_from_capability(server, params)
except (ValueError, KeyError, LookupError, FileNotFoundError) as exc:
@@ -482,7 +485,7 @@ def register_methods(
)
async def workflow_draft_workspaces_compile(
params: CompileDraftWorkspaceParams = RpcParams(),
) -> dict[str, Any]:
) -> CompileDraftWorkspaceResult:
try:
return await server.api.compile_draft_workspace(
workspace_id=params.workspace_id,
@@ -556,7 +559,7 @@ def register_methods(
)
async def workflow_draft_workspaces_create_artifact(
params: CreateArtifactFromWorkspaceParams = RpcParams(),
) -> dict[str, Any]:
) -> CreateArtifactFromWorkspaceResult:
try:
return await server.api.create_artifact_from_workspace(
workspace_id=params.workspace_id,
@@ -578,7 +581,7 @@ def register_methods(
)
async def workflow_draft_workspaces_create_wrapper(
params: CreateWrapperFromWorkspaceParams = RpcParams(),
) -> dict[str, Any]:
) -> CreateArtifactFromWorkspaceResult:
try:
return await server.api.create_wrapper_from_workspace(
workspace_id=params.workspace_id,
@@ -598,7 +601,7 @@ def register_methods(
async def _create_from_capability(
server: WorkflowServer,
params: CreateDraftFromCapabilityParams,
) -> dict[str, Any]:
) -> CreateDraftWorkspaceFromCapabilityResult:
return await server.api.create_draft_workspace_from_capability(
workspace_id=params.workspace_id,
capability_name=params.capability_name,
@@ -202,6 +202,95 @@ def test_openrpc_pins_nested_draft_workspace_contract(
assert deleted["properties"]["status"]["enum"] == ["deleted", "not_found"]
def test_openrpc_exposes_typed_compile_draft_workspace_result(
openrpc_document: dict[str, Any],
) -> None:
method = _method_by_name(
openrpc_document,
"workflow.draft_workspaces.compile",
)
assert method["result"]["schema"] == {
"$ref": "#/components/schemas/CompileDraftWorkspaceResult"
}
assert openrpc_document["components"]["schemas"]["CompileDraftWorkspaceResult"] == {
"anyOf": [
{"$ref": ("#/components/schemas/CompileDraftWorkspaceSuccess")},
{"$ref": "#/components/schemas/InvalidDraftResult"},
]
}
schemas = openrpc_document["components"]["schemas"]
assert schemas["CompileDraftWorkspaceSuccess"]["required"] == [
"compiled_plan",
"required_capabilities",
]
assert schemas["InvalidDraftResult"]["properties"]["status"]["const"] == "invalid"
def test_openrpc_exposes_typed_capability_bootstrap_result(
openrpc_document: dict[str, Any],
) -> None:
for method_name in (
"workflow.drafts.create_from_capability",
"workflow.draft_workspaces.create_from_capability",
):
_assert_result_component(
openrpc_document,
method_name=method_name,
component_name="CreateDraftWorkspaceFromCapabilityResult",
properties={
"workspace_id",
"revision",
"wrapper_hints",
"next_actions",
},
)
result = openrpc_document["components"]["schemas"][
"CreateDraftWorkspaceFromCapabilityResult"
]
assert result["properties"]["wrapper_hints"] == {
"$ref": "#/components/schemas/WrapperAuthoringHintsPayload"
}
assert result["properties"]["next_actions"] == {
"$ref": "#/components/schemas/NextActionsPayload"
}
@pytest.mark.parametrize(
"method_name",
[
"workflow.draft_workspaces.create_artifact",
"workflow.draft_workspaces.create_wrapper",
],
)
def test_openrpc_exposes_typed_workspace_artifact_save_result(
openrpc_document: dict[str, Any],
method_name: str,
) -> None:
method = _method_by_name(openrpc_document, method_name)
assert method["result"]["schema"] == {
"$ref": "#/components/schemas/CreateArtifactFromWorkspaceResult"
}
assert openrpc_document["components"]["schemas"][
"CreateArtifactFromWorkspaceResult"
] == {
"anyOf": [
{"$ref": "#/components/schemas/SavedDraftArtifactResult"},
{"$ref": "#/components/schemas/UnsavedDraftArtifactResult"},
]
}
schemas = openrpc_document["components"]["schemas"]
assert schemas["SavedDraftArtifactResult"]["properties"]["saved"]["const"] is True
assert {
"required_logical_sources",
"suggested_bindings",
} <= schemas["SavedDraftArtifactResult"]["properties"].keys()
assert (
schemas["UnsavedDraftArtifactResult"]["properties"]["saved"]["const"] is False
)
@pytest.mark.parametrize(
("method_name", "component_name", "properties"),
[