feat: type source registry results
This commit is contained in:
@@ -74,10 +74,10 @@
|
||||
- Typed-result slices now give `workflow.health`, all artifact, deployment,
|
||||
and run operations, every persisted draft-workspace operation, and both the
|
||||
capability and source-discovery surfaces named transport-neutral result
|
||||
schemas: 55 of 70 methods. The remaining 15 success results still collapse
|
||||
to generic objects across the source-registry/admin operations. Continue
|
||||
introducing operation result DTOs before adopting generated TypeScript
|
||||
contracts.
|
||||
schemas: 63 of 70 methods. The remaining seven success results still
|
||||
collapse to generic objects across connections, events, and auth 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.
|
||||
|
||||
@@ -79,6 +79,15 @@ from .runs import (
|
||||
TraceEntryPayload,
|
||||
WorkflowRefPayload,
|
||||
)
|
||||
from .source_registry import (
|
||||
ApplyRegistryChangesResult,
|
||||
InspectRegistryEntryResult,
|
||||
ListRegistryEntriesResult,
|
||||
RegistryEntryMutationResult,
|
||||
RegistryEntryPayload,
|
||||
RegistryEntrySummaryPayload,
|
||||
RemoveRegistryEntryResult,
|
||||
)
|
||||
from .sources import (
|
||||
InspectSourceResult,
|
||||
ListSourcesResult,
|
||||
@@ -99,6 +108,7 @@ from .sources import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ApplyRegistryChangesResult",
|
||||
"ArtifactVersionPayload",
|
||||
"ArtifactCatalogEntryPayload",
|
||||
"ArtifactKindPayload",
|
||||
@@ -125,6 +135,7 @@ __all__ = [
|
||||
"InterruptRoutePayload",
|
||||
"InvalidDraftResult",
|
||||
"InspectCapabilityResult",
|
||||
"InspectRegistryEntryResult",
|
||||
"InspectSourceResult",
|
||||
"JsonObject",
|
||||
"JsonProjector",
|
||||
@@ -134,6 +145,7 @@ __all__ = [
|
||||
"ListArtifactsResult",
|
||||
"ListCapabilitiesResult",
|
||||
"ListRunsResult",
|
||||
"ListRegistryEntriesResult",
|
||||
"ListSourcesResult",
|
||||
"NextActionPatchExamplePayload",
|
||||
"NextActionsPayload",
|
||||
@@ -145,6 +157,9 @@ __all__ = [
|
||||
"PatchedDraftInvalidResult",
|
||||
"PatchedDraftValidResult",
|
||||
"RawWorkflowPlan",
|
||||
"RegistryEntryMutationResult",
|
||||
"RegistryEntryPayload",
|
||||
"RegistryEntrySummaryPayload",
|
||||
"ReducerInventoryPayload",
|
||||
"ResumeReadiness",
|
||||
"RunResult",
|
||||
@@ -152,6 +167,7 @@ __all__ = [
|
||||
"RunSummary",
|
||||
"RunTraceResult",
|
||||
"RequiredCapabilityPayload",
|
||||
"RemoveRegistryEntryResult",
|
||||
"SaveArtifactResult",
|
||||
"SavedDraftArtifactResult",
|
||||
"SaveDeploymentResult",
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import NotRequired, TypedDict
|
||||
|
||||
from pydantic import ConfigDict, with_config
|
||||
|
||||
from .common import DependencyDiagnosticPayload, JsonObject, PageMetadataPayload
|
||||
|
||||
|
||||
@with_config(ConfigDict(extra="allow"))
|
||||
class RegistryEntryPayload(TypedDict):
|
||||
"""Desired source entry with provider-specific configuration preserved."""
|
||||
|
||||
id: str
|
||||
kind: str
|
||||
enabled: bool
|
||||
provider: NotRequired[str | None]
|
||||
account: NotRequired[str | None]
|
||||
profile: NotRequired[str | None]
|
||||
transport: NotRequired[JsonObject | None]
|
||||
auth_ref: NotRequired[str | None]
|
||||
metadata: NotRequired[JsonObject]
|
||||
|
||||
|
||||
class RegistryEntrySummaryPayload(TypedDict):
|
||||
"""Compact desired-source row including config precedence facts."""
|
||||
|
||||
id: str
|
||||
kind: str
|
||||
enabled: bool
|
||||
provider: str | None
|
||||
account: str | None
|
||||
profile: str | None
|
||||
transport_kind: str | None
|
||||
auth_ref: str | None
|
||||
shadowed_by_config: bool
|
||||
config_ownership: str | None
|
||||
mutable: bool
|
||||
|
||||
|
||||
class ListRegistryEntriesResult(PageMetadataPayload):
|
||||
"""Cursor-paged desired source registry entries."""
|
||||
|
||||
entries: list[RegistryEntrySummaryPayload]
|
||||
|
||||
|
||||
class InspectRegistryEntryResult(TypedDict):
|
||||
"""Full desired source entry with config precedence facts."""
|
||||
|
||||
entry: RegistryEntryPayload
|
||||
shadowed_by_config: bool
|
||||
config_ownership: str | None
|
||||
mutable: bool
|
||||
|
||||
|
||||
class RegistryEntryMutationResult(TypedDict):
|
||||
"""Desired source entry returned after add, update, or enablement changes."""
|
||||
|
||||
entry: RegistryEntryPayload
|
||||
shadowed_by_config: bool
|
||||
|
||||
|
||||
class RemoveRegistryEntryResult(TypedDict):
|
||||
"""Outcome of removing one desired source registry entry."""
|
||||
|
||||
removed: bool
|
||||
source_id: str
|
||||
|
||||
|
||||
@with_config(ConfigDict(extra="allow"))
|
||||
class ApplyRegistryChangesResult(TypedDict):
|
||||
"""Summary of reconciling desired registry state into the live service."""
|
||||
|
||||
applied: bool
|
||||
registered: list[str]
|
||||
updated: list[str]
|
||||
removed: list[str]
|
||||
connection_count: int
|
||||
registry_entry_count: int
|
||||
auth_diagnostics: NotRequired[list[DependencyDiagnosticPayload]]
|
||||
@@ -6,6 +6,21 @@ from typing import Any, Protocol, runtime_checkable
|
||||
|
||||
from wf_platform import page_items
|
||||
|
||||
from .models import (
|
||||
ApplyRegistryChangesResult,
|
||||
InspectRegistryEntryResult,
|
||||
JsonProjector,
|
||||
ListRegistryEntriesResult,
|
||||
RegistryEntryMutationResult,
|
||||
RemoveRegistryEntryResult,
|
||||
)
|
||||
|
||||
_PROJECT_REGISTRY_LIST = JsonProjector(ListRegistryEntriesResult)
|
||||
_PROJECT_REGISTRY_INSPECT = JsonProjector(InspectRegistryEntryResult)
|
||||
_PROJECT_REGISTRY_MUTATION = JsonProjector(RegistryEntryMutationResult)
|
||||
_PROJECT_REGISTRY_REMOVE = JsonProjector(RemoveRegistryEntryResult)
|
||||
_PROJECT_REGISTRY_APPLY = JsonProjector(ApplyRegistryChangesResult)
|
||||
|
||||
|
||||
class WorkflowSourceRegistryProvider(Protocol):
|
||||
"""Provides desired source registry state for read-only admin frontends."""
|
||||
@@ -68,7 +83,7 @@ class WorkflowSourceRegistryApi:
|
||||
*,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
) -> ListRegistryEntriesResult:
|
||||
ownership = self._provider.config_source_ownership()
|
||||
entries = sorted(
|
||||
(
|
||||
@@ -80,104 +95,109 @@ class WorkflowSourceRegistryApi:
|
||||
key=lambda item: str(item.get("id", "")),
|
||||
)
|
||||
page = page_items(entries, cursor=cursor, limit=limit)
|
||||
return {
|
||||
"entries": list(page.items),
|
||||
"next_cursor": page.next_cursor,
|
||||
"total": page.total,
|
||||
}
|
||||
return _PROJECT_REGISTRY_LIST(
|
||||
{
|
||||
"entries": list(page.items),
|
||||
"next_cursor": page.next_cursor,
|
||||
"total": page.total,
|
||||
}
|
||||
)
|
||||
|
||||
async def inspect_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> InspectRegistryEntryResult:
|
||||
ownership = self._provider.config_source_ownership()
|
||||
for item in self._provider.list_registry_entries():
|
||||
entry = _payload(item)
|
||||
if entry.get("id") == source_id:
|
||||
return {
|
||||
"entry": entry,
|
||||
"shadowed_by_config": self._is_shadowed(source_id),
|
||||
"config_ownership": ownership.get(source_id),
|
||||
"mutable": ownership.get(source_id) != "locked",
|
||||
}
|
||||
return _PROJECT_REGISTRY_INSPECT(
|
||||
{
|
||||
"entry": entry,
|
||||
"shadowed_by_config": self._is_shadowed(source_id),
|
||||
"config_ownership": ownership.get(source_id),
|
||||
"mutable": ownership.get(source_id) != "locked",
|
||||
}
|
||||
)
|
||||
raise KeyError(f"unknown registry source {source_id!r}")
|
||||
|
||||
async def add_registry_entry(
|
||||
self,
|
||||
*,
|
||||
entry: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> RegistryEntryMutationResult:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("add_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.add_registry_entry(entry)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"entry": result,
|
||||
"shadowed_by_config": self._is_shadowed(result["id"]),
|
||||
}
|
||||
return self._mutation_result(result)
|
||||
|
||||
async def update_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
patch: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
) -> RegistryEntryMutationResult:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("update_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.update_registry_entry(source_id, patch)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"entry": result,
|
||||
"shadowed_by_config": self._is_shadowed(result["id"]),
|
||||
}
|
||||
return self._mutation_result(result)
|
||||
|
||||
async def enable_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> RegistryEntryMutationResult:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("enable_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.set_registry_entry_enabled(source_id, True)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"entry": result,
|
||||
"shadowed_by_config": self._is_shadowed(result["id"]),
|
||||
}
|
||||
return self._mutation_result(result)
|
||||
|
||||
async def disable_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> RegistryEntryMutationResult:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("disable_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.set_registry_entry_enabled(source_id, False)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"entry": result,
|
||||
"shadowed_by_config": self._is_shadowed(result["id"]),
|
||||
}
|
||||
return self._mutation_result(result)
|
||||
|
||||
async def remove_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
) -> RemoveRegistryEntryResult:
|
||||
if self._mutation_provider is None:
|
||||
raise TypeError("remove_registry_entry requires a mutation provider")
|
||||
raw = self._mutation_provider.remove_registry_entry(source_id)
|
||||
result = _payload(raw)
|
||||
return {
|
||||
"removed": bool(result.get("removed")),
|
||||
"source_id": str(result.get("source_id", source_id)),
|
||||
}
|
||||
return _PROJECT_REGISTRY_REMOVE(
|
||||
{
|
||||
"removed": bool(result.get("removed")),
|
||||
"source_id": str(result.get("source_id", source_id)),
|
||||
}
|
||||
)
|
||||
|
||||
async def apply_registry_changes(self) -> dict[str, Any]:
|
||||
async def apply_registry_changes(self) -> ApplyRegistryChangesResult:
|
||||
if self._apply_provider is None:
|
||||
raise TypeError("apply_registry_changes requires an apply provider")
|
||||
return _payload(self._apply_provider.apply_registry_changes())
|
||||
return _PROJECT_REGISTRY_APPLY(
|
||||
_payload(self._apply_provider.apply_registry_changes())
|
||||
)
|
||||
|
||||
def _mutation_result(self, entry: dict[str, Any]) -> RegistryEntryMutationResult:
|
||||
"""Attach config precedence to one provider-validated mutation entry."""
|
||||
return _PROJECT_REGISTRY_MUTATION(
|
||||
{
|
||||
"entry": entry,
|
||||
"shadowed_by_config": self._is_shadowed(entry["id"]),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _payload(value: Mapping[str, Any] | object) -> dict[str, Any]:
|
||||
|
||||
+13
-8
@@ -10,6 +10,7 @@ from wf_core.models.steps import InputBinding, OutputBinding
|
||||
from .draft_authoring import RouteSource
|
||||
from .draft_updates import CapabilityStepUpdate
|
||||
from .models import (
|
||||
ApplyRegistryChangesResult,
|
||||
CapabilityCallResult,
|
||||
CompileDraftWorkspaceResult,
|
||||
CreateArtifactFromWorkspaceResult,
|
||||
@@ -19,14 +20,18 @@ from .models import (
|
||||
DeleteDraftWorkspaceResult,
|
||||
DraftWorkspaceResult,
|
||||
InspectCapabilityResult,
|
||||
InspectRegistryEntryResult,
|
||||
InspectSourceResult,
|
||||
ListArtifactsResult,
|
||||
ListCapabilitiesResult,
|
||||
ListDeploymentsResult,
|
||||
ListDraftWorkspacesResult,
|
||||
ListRegistryEntriesResult,
|
||||
ListRunsResult,
|
||||
ListSourcesResult,
|
||||
PatchDraftResult,
|
||||
RegistryEntryMutationResult,
|
||||
RemoveRegistryEntryResult,
|
||||
RunResult,
|
||||
RunTraceResult,
|
||||
SaveArtifactResult,
|
||||
@@ -562,46 +567,46 @@ class WorkflowSourceRegistrySurface(Protocol):
|
||||
*,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> ListRegistryEntriesResult: ...
|
||||
|
||||
async def inspect_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> InspectRegistryEntryResult: ...
|
||||
|
||||
async def add_registry_entry(
|
||||
self,
|
||||
*,
|
||||
entry: dict[str, Any],
|
||||
) -> dict[str, Any]: ...
|
||||
) -> RegistryEntryMutationResult: ...
|
||||
|
||||
async def update_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
patch: dict[str, Any],
|
||||
) -> dict[str, Any]: ...
|
||||
) -> RegistryEntryMutationResult: ...
|
||||
|
||||
async def enable_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> RegistryEntryMutationResult: ...
|
||||
|
||||
async def disable_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> RegistryEntryMutationResult: ...
|
||||
|
||||
async def remove_registry_entry(
|
||||
self,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]: ...
|
||||
) -> RemoveRegistryEntryResult: ...
|
||||
|
||||
async def apply_registry_changes(self) -> dict[str, Any]: ...
|
||||
async def apply_registry_changes(self) -> ApplyRegistryChangesResult: ...
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
|
||||
from wf_api.models import (
|
||||
ApplyRegistryChangesResult,
|
||||
InspectRegistryEntryResult,
|
||||
ListRegistryEntriesResult,
|
||||
RegistryEntryMutationResult,
|
||||
RemoveRegistryEntryResult,
|
||||
)
|
||||
|
||||
from .base import RpcCaller
|
||||
|
||||
@@ -13,30 +21,39 @@ class RpcSourceRegistryClientMixin:
|
||||
*,
|
||||
cursor: str | None = None,
|
||||
limit: int = 50,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.admin.source_registry.list",
|
||||
{"cursor": cursor, "limit": limit},
|
||||
) -> ListRegistryEntriesResult:
|
||||
return cast(
|
||||
ListRegistryEntriesResult,
|
||||
await self._call(
|
||||
"workflow.admin.source_registry.list",
|
||||
{"cursor": cursor, "limit": limit},
|
||||
),
|
||||
)
|
||||
|
||||
async def inspect_registry_entry(
|
||||
self: RpcCaller,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.admin.source_registry.inspect",
|
||||
{"source_id": source_id},
|
||||
) -> InspectRegistryEntryResult:
|
||||
return cast(
|
||||
InspectRegistryEntryResult,
|
||||
await self._call(
|
||||
"workflow.admin.source_registry.inspect",
|
||||
{"source_id": source_id},
|
||||
),
|
||||
)
|
||||
|
||||
async def add_registry_entry(
|
||||
self: RpcCaller,
|
||||
*,
|
||||
entry: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.admin.source_registry.add",
|
||||
{"entry": entry},
|
||||
) -> RegistryEntryMutationResult:
|
||||
return cast(
|
||||
RegistryEntryMutationResult,
|
||||
await self._call(
|
||||
"workflow.admin.source_registry.add",
|
||||
{"entry": entry},
|
||||
),
|
||||
)
|
||||
|
||||
async def update_registry_entry(
|
||||
@@ -44,44 +61,59 @@ class RpcSourceRegistryClientMixin:
|
||||
*,
|
||||
source_id: str,
|
||||
patch: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.admin.source_registry.update",
|
||||
{"source_id": source_id, "patch": patch},
|
||||
) -> RegistryEntryMutationResult:
|
||||
return cast(
|
||||
RegistryEntryMutationResult,
|
||||
await self._call(
|
||||
"workflow.admin.source_registry.update",
|
||||
{"source_id": source_id, "patch": patch},
|
||||
),
|
||||
)
|
||||
|
||||
async def enable_registry_entry(
|
||||
self: RpcCaller,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.admin.source_registry.enable",
|
||||
{"source_id": source_id},
|
||||
) -> RegistryEntryMutationResult:
|
||||
return cast(
|
||||
RegistryEntryMutationResult,
|
||||
await self._call(
|
||||
"workflow.admin.source_registry.enable",
|
||||
{"source_id": source_id},
|
||||
),
|
||||
)
|
||||
|
||||
async def disable_registry_entry(
|
||||
self: RpcCaller,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.admin.source_registry.disable",
|
||||
{"source_id": source_id},
|
||||
) -> RegistryEntryMutationResult:
|
||||
return cast(
|
||||
RegistryEntryMutationResult,
|
||||
await self._call(
|
||||
"workflow.admin.source_registry.disable",
|
||||
{"source_id": source_id},
|
||||
),
|
||||
)
|
||||
|
||||
async def remove_registry_entry(
|
||||
self: RpcCaller,
|
||||
*,
|
||||
source_id: str,
|
||||
) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.admin.source_registry.remove",
|
||||
{"source_id": source_id},
|
||||
) -> RemoveRegistryEntryResult:
|
||||
return cast(
|
||||
RemoveRegistryEntryResult,
|
||||
await self._call(
|
||||
"workflow.admin.source_registry.remove",
|
||||
{"source_id": source_id},
|
||||
),
|
||||
)
|
||||
|
||||
async def apply_registry_changes(self: RpcCaller) -> dict[str, Any]:
|
||||
return await self._call(
|
||||
"workflow.admin.source_registry.apply",
|
||||
{},
|
||||
async def apply_registry_changes(self: RpcCaller) -> ApplyRegistryChangesResult:
|
||||
return cast(
|
||||
ApplyRegistryChangesResult,
|
||||
await self._call(
|
||||
"workflow.admin.source_registry.apply",
|
||||
{},
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
from __future__ import annotations
|
||||
"""Source registry JSON-RPC method registration.
|
||||
|
||||
from typing import Any
|
||||
Return annotations stay eagerly evaluated because fastapi-jsonrpc captures them
|
||||
while registering nested handlers for response validation and OpenRPC output.
|
||||
"""
|
||||
|
||||
import fastapi_jsonrpc as jsonrpc
|
||||
|
||||
from wf_api import WorkflowSourceRegistrySurface
|
||||
from wf_api.models import (
|
||||
ApplyRegistryChangesResult,
|
||||
InspectRegistryEntryResult,
|
||||
ListRegistryEntriesResult,
|
||||
RegistryEntryMutationResult,
|
||||
RemoveRegistryEntryResult,
|
||||
)
|
||||
from wf_server import WorkflowServer
|
||||
|
||||
from ..errors import WorkflowRpcError, raise_workflow_rpc_error
|
||||
@@ -51,7 +60,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_admin_source_registry_list(
|
||||
params: ListRegistryEntriesParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> ListRegistryEntriesResult:
|
||||
admin = _require_source_registry_admin(server, operation="reads")
|
||||
try:
|
||||
return await admin.list_registry_entries(
|
||||
@@ -67,7 +76,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_admin_source_registry_inspect(
|
||||
params: InspectRegistryEntryParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> InspectRegistryEntryResult:
|
||||
admin = _require_source_registry_admin(server, operation="reads")
|
||||
try:
|
||||
return await admin.inspect_registry_entry(
|
||||
@@ -82,7 +91,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_admin_source_registry_add(
|
||||
params: AddRegistryEntryParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> RegistryEntryMutationResult:
|
||||
admin = _require_source_registry_admin(server, operation="mutations")
|
||||
try:
|
||||
return await admin.add_registry_entry(
|
||||
@@ -97,7 +106,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_admin_source_registry_update(
|
||||
params: UpdateRegistryEntryParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> RegistryEntryMutationResult:
|
||||
admin = _require_source_registry_admin(server, operation="mutations")
|
||||
try:
|
||||
return await admin.update_registry_entry(
|
||||
@@ -113,7 +122,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_admin_source_registry_enable(
|
||||
params: RegistryEntryIdParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> RegistryEntryMutationResult:
|
||||
admin = _require_source_registry_admin(server, operation="mutations")
|
||||
try:
|
||||
return await admin.enable_registry_entry(
|
||||
@@ -128,7 +137,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_admin_source_registry_disable(
|
||||
params: RegistryEntryIdParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> RegistryEntryMutationResult:
|
||||
admin = _require_source_registry_admin(server, operation="mutations")
|
||||
try:
|
||||
return await admin.disable_registry_entry(
|
||||
@@ -143,7 +152,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_admin_source_registry_remove(
|
||||
params: RegistryEntryIdParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> RemoveRegistryEntryResult:
|
||||
admin = _require_source_registry_admin(server, operation="mutations")
|
||||
try:
|
||||
return await admin.remove_registry_entry(
|
||||
@@ -158,7 +167,7 @@ def register_methods(
|
||||
)
|
||||
async def workflow_admin_source_registry_apply(
|
||||
params: ApplyRegistryChangesParams = RpcParams(),
|
||||
) -> dict[str, Any]:
|
||||
) -> ApplyRegistryChangesResult:
|
||||
admin = _require_source_registry_admin(server, operation="apply")
|
||||
try:
|
||||
return await admin.apply_registry_changes()
|
||||
|
||||
@@ -169,6 +169,121 @@ def test_openrpc_exposes_typed_source_diagnosis_result(
|
||||
assert diagnosed["additionalProperties"] is True
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_source_registry_read_results(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name="workflow.admin.source_registry.list",
|
||||
component_name="ListRegistryEntriesResult",
|
||||
properties={"entries", "next_cursor", "total"},
|
||||
)
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name="workflow.admin.source_registry.inspect",
|
||||
component_name="InspectRegistryEntryResult",
|
||||
properties={"entry", "shadowed_by_config", "config_ownership", "mutable"},
|
||||
)
|
||||
schemas = openrpc_document["components"]["schemas"]
|
||||
assert schemas["ListRegistryEntriesResult"]["properties"]["entries"]["items"] == {
|
||||
"$ref": "#/components/schemas/RegistryEntrySummaryPayload"
|
||||
}
|
||||
assert schemas["InspectRegistryEntryResult"]["properties"]["entry"] == {
|
||||
"$ref": "#/components/schemas/RegistryEntryPayload"
|
||||
}
|
||||
assert schemas["RegistryEntryPayload"]["additionalProperties"] is True
|
||||
assert set(schemas["ListRegistryEntriesResult"]["required"]) == {
|
||||
"entries",
|
||||
"next_cursor",
|
||||
"total",
|
||||
}
|
||||
assert set(schemas["RegistryEntrySummaryPayload"]["required"]) == {
|
||||
"id",
|
||||
"kind",
|
||||
"enabled",
|
||||
"provider",
|
||||
"account",
|
||||
"profile",
|
||||
"transport_kind",
|
||||
"auth_ref",
|
||||
"shadowed_by_config",
|
||||
"config_ownership",
|
||||
"mutable",
|
||||
}
|
||||
assert set(schemas["InspectRegistryEntryResult"]["required"]) == {
|
||||
"entry",
|
||||
"shadowed_by_config",
|
||||
"config_ownership",
|
||||
"mutable",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"method_name",
|
||||
[
|
||||
"workflow.admin.source_registry.add",
|
||||
"workflow.admin.source_registry.update",
|
||||
"workflow.admin.source_registry.enable",
|
||||
"workflow.admin.source_registry.disable",
|
||||
],
|
||||
)
|
||||
def test_openrpc_exposes_typed_source_registry_mutation_result(
|
||||
openrpc_document: dict[str, Any],
|
||||
method_name: str,
|
||||
) -> None:
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name=method_name,
|
||||
component_name="RegistryEntryMutationResult",
|
||||
properties={"entry", "shadowed_by_config"},
|
||||
)
|
||||
mutation = openrpc_document["components"]["schemas"]["RegistryEntryMutationResult"]
|
||||
assert set(mutation["required"]) == {"entry", "shadowed_by_config"}
|
||||
assert mutation["properties"]["entry"] == {
|
||||
"$ref": "#/components/schemas/RegistryEntryPayload"
|
||||
}
|
||||
|
||||
|
||||
def test_openrpc_exposes_typed_source_registry_remove_and_apply_results(
|
||||
openrpc_document: dict[str, Any],
|
||||
) -> None:
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name="workflow.admin.source_registry.remove",
|
||||
component_name="RemoveRegistryEntryResult",
|
||||
properties={"removed", "source_id"},
|
||||
)
|
||||
_assert_result_component(
|
||||
openrpc_document,
|
||||
method_name="workflow.admin.source_registry.apply",
|
||||
component_name="ApplyRegistryChangesResult",
|
||||
properties={
|
||||
"applied",
|
||||
"registered",
|
||||
"updated",
|
||||
"removed",
|
||||
"connection_count",
|
||||
"registry_entry_count",
|
||||
},
|
||||
)
|
||||
schemas = openrpc_document["components"]["schemas"]
|
||||
assert set(schemas["RemoveRegistryEntryResult"]["required"]) == {
|
||||
"removed",
|
||||
"source_id",
|
||||
}
|
||||
assert set(schemas["ApplyRegistryChangesResult"]["required"]) == {
|
||||
"applied",
|
||||
"registered",
|
||||
"updated",
|
||||
"removed",
|
||||
"connection_count",
|
||||
"registry_entry_count",
|
||||
}
|
||||
assert schemas["ApplyRegistryChangesResult"]["properties"]["auth_diagnostics"][
|
||||
"items"
|
||||
] == {"$ref": "#/components/schemas/DependencyDiagnosticPayload"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("method_name", "component_name", "properties"),
|
||||
[
|
||||
|
||||
@@ -22,6 +22,7 @@ class FakeRegistryEntry:
|
||||
transport: dict[str, str] | None = None
|
||||
auth_ref: str | None = "github.work"
|
||||
metadata: dict[str, object] | None = None
|
||||
provider_options: dict[str, object] | None = None
|
||||
|
||||
|
||||
class FakeRegistryProvider:
|
||||
@@ -31,6 +32,7 @@ class FakeRegistryProvider:
|
||||
id="github.work",
|
||||
transport={"kind": "stdio", "command": "npx"},
|
||||
metadata={},
|
||||
provider_options={"region": "work"},
|
||||
)
|
||||
]
|
||||
|
||||
@@ -152,6 +154,8 @@ async def test_rpc_source_registry_methods_return_registry_payloads(tmp_path) ->
|
||||
assert listed["result"]["entries"][0]["id"] == "github.work"
|
||||
assert listed["result"]["entries"][0]["shadowed_by_config"] is True
|
||||
assert inspected["result"]["entry"]["transport"]["kind"] == "stdio"
|
||||
assert inspected["result"]["entry"]["transport"]["command"] == "npx"
|
||||
assert inspected["result"]["entry"]["provider_options"] == {"region": "work"}
|
||||
assert inspected["result"]["shadowed_by_config"] is True
|
||||
|
||||
|
||||
@@ -246,12 +250,22 @@ async def test_rpc_source_registry_add_returns_entry(tmp_path) -> None:
|
||||
payload = await _rpc(
|
||||
client,
|
||||
"workflow.admin.source_registry.add",
|
||||
{"entry": {"id": "new.mcp", "kind": "mcp", "enabled": True}},
|
||||
{
|
||||
"entry": {
|
||||
"id": "new.mcp",
|
||||
"kind": "mcp",
|
||||
"enabled": True,
|
||||
"transport": {"kind": "stdio", "command": "custom-runner"},
|
||||
"provider_options": {"region": "local"},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
assert "result" in payload
|
||||
assert payload["result"]["entry"]["id"] == "new.mcp"
|
||||
assert payload["result"]["entry"]["kind"] == "mcp"
|
||||
assert payload["result"]["entry"]["transport"]["command"] == "custom-runner"
|
||||
assert payload["result"]["entry"]["provider_options"] == {"region": "local"}
|
||||
|
||||
|
||||
async def test_rpc_source_registry_update_returns_entry(tmp_path) -> None:
|
||||
@@ -476,6 +490,16 @@ async def test_rpc_source_registry_apply_returns_summary(tmp_path) -> None:
|
||||
"removed": [],
|
||||
"connection_count": 1,
|
||||
"registry_entry_count": 1,
|
||||
"auth_diagnostics": [
|
||||
{
|
||||
"severity": "error",
|
||||
"code": "auth_not_found",
|
||||
"logical_ref": "",
|
||||
"bound_source": "demo.new",
|
||||
"message": "Missing auth record.",
|
||||
"repair_hint": "Save the referenced auth record.",
|
||||
}
|
||||
],
|
||||
}
|
||||
server = replace(
|
||||
build_local_static_workflow_server(tmp_path / "store"),
|
||||
@@ -494,6 +518,7 @@ async def test_rpc_source_registry_apply_returns_summary(tmp_path) -> None:
|
||||
|
||||
assert payload["result"]["applied"] is True
|
||||
assert payload["result"]["registered"] == ["demo.new"]
|
||||
assert payload["result"]["auth_diagnostics"][0]["code"] == "auth_not_found"
|
||||
admin.apply_registry_changes.assert_awaited_once()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user