feat: type source registry results

This commit is contained in:
lda
2026-08-01 09:02:56 +07:00 Verified
parent fbcd3a61a5
commit 18b9002d17
9 changed files with 398 additions and 96 deletions
+4 -4
View File
@@ -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.
+16
View File
@@ -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",
+80
View File
@@ -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]]
+60 -40
View File
@@ -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
View File
@@ -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()