From 58c552c183dfcb400ecf3d13ce89c6237780a658 Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Wed, 21 Oct 2026 10:28:30 -0700 Subject: [PATCH 1/3] Sent unknown System Nexus operations to the generic interceptor hook. The generated dispatch raised for an operation that it did not generate, so an outbound interceptor could not serve its own System Nexus service. nexgen 0.2.10 passes such an operation to start_system_nexus_operation, so the pin moves to it and the bindings are regenerated. --- .github/workflows/ci.yml | 2 +- scripts/gen_nexus_system_api.py | 2 +- temporalio/nexus/notifications/__init__.py | 2 +- temporalio/nexus/notifications/models.py | 2 +- .../nexus/system/workflow_service/__init__.py | 2 +- .../_system_nexus_interceptor.py | 14 ++- .../nexus/system/workflow_service/models.py | 11 ++- .../workflow_service/operations/__init__.py | 2 +- .../operations/signal_with_start_workflow.py | 2 +- .../nexus/system/workflow_service/services.py | 2 +- tests/nexus/test_system_nexus_fallthrough.py | 85 +++++++++++++++++++ 11 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 tests/nexus/test_system_nexus_fallthrough.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 613f923b2e..cb5a1653f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -229,7 +229,7 @@ jobs: - run: uv add --dev --python 3.10 "googleapis-common-protos==1.70.0" - run: uv add --python 3.10 "protobuf<4" - run: uv sync --all-extras - - run: cargo install --locked nexgen --version 0.2.8 --features advanced --force + - run: cargo install --locked nexgen --version 0.2.10 --features advanced --force - run: poe build-develop - run: poe gen-protos - name: Check generation unchanged diff --git a/scripts/gen_nexus_system_api.py b/scripts/gen_nexus_system_api.py index be72cf37a9..c2a9deda60 100644 --- a/scripts/gen_nexus_system_api.py +++ b/scripts/gen_nexus_system_api.py @@ -25,7 +25,7 @@ workflow_output_dir = base_dir / "temporalio" / "nexus" / "system" / "workflow_service" notification_output_dir = base_dir / "temporalio" / "nexus" / "notifications" workflow_init_path = base_dir / "temporalio" / "workflow" / "__init__.py" -NEX_GEN_VERSION = "0.2.8" +NEX_GEN_VERSION = "0.2.10" proto_files = [ gen_protos.api_proto_dir / "temporal" diff --git a/temporalio/nexus/notifications/__init__.py b/temporalio/nexus/notifications/__init__.py index 7a8bb24ead..d5ff1b8dac 100644 --- a/temporalio/nexus/notifications/__init__.py +++ b/temporalio/nexus/notifications/__init__.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.8. DO NOT EDIT! +# Generated by nexgen v0.2.10. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/notifications/models.py b/temporalio/nexus/notifications/models.py index 64ad07fbae..a94cffaef0 100644 --- a/temporalio/nexus/notifications/models.py +++ b/temporalio/nexus/notifications/models.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.8. DO NOT EDIT! +# Generated by nexgen v0.2.10. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/__init__.py b/temporalio/nexus/system/workflow_service/__init__.py index f6a6f0c682..84fb38a80f 100644 --- a/temporalio/nexus/system/workflow_service/__init__.py +++ b/temporalio/nexus/system/workflow_service/__init__.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.8. DO NOT EDIT! +# Generated by nexgen v0.2.10. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/_system_nexus_interceptor.py b/temporalio/nexus/system/workflow_service/_system_nexus_interceptor.py index 5c62e00927..e5c058079e 100644 --- a/temporalio/nexus/system/workflow_service/_system_nexus_interceptor.py +++ b/temporalio/nexus/system/workflow_service/_system_nexus_interceptor.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.8. DO NOT EDIT! +# Generated by nexgen v0.2.10. DO NOT EDIT! from __future__ import annotations @@ -40,9 +40,7 @@ async def _start_system_nexus_operation( "temporalio.workflow.NexusOperationHandle[_OutputT]", await interceptor.start_signal_with_start_workflow(typed_input.input), ) - raise ValueError( - f"unsupported System Nexus operation: {input.service}/{input.operation_name}" - ) + return await interceptor.start_system_nexus_operation(input) class _SystemNexusWorkflowOutboundInterceptorBase(abc.ABC): @@ -51,6 +49,14 @@ def _next_system_nexus_interceptor( self, ) -> _SystemNexusWorkflowOutboundInterceptorBase: ... + @abc.abstractmethod + async def start_system_nexus_operation( + self, + input: StartNexusOperationInput[_InputT, _OutputT], + ) -> temporalio.workflow.NexusOperationHandle[_OutputT]: + """Intercept a System Nexus operation through the generic hook.""" + ... + async def start_signal_with_start_workflow( self, request: models.SignalWithStartWorkflowRequest ) -> temporalio.workflow.NexusOperationHandle[ diff --git a/temporalio/nexus/system/workflow_service/models.py b/temporalio/nexus/system/workflow_service/models.py index dc7b5ce2e7..87efea1abe 100644 --- a/temporalio/nexus/system/workflow_service/models.py +++ b/temporalio/nexus/system/workflow_service/models.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.8. DO NOT EDIT! +# Generated by nexgen v0.2.10. DO NOT EDIT! from __future__ import annotations @@ -18,7 +18,8 @@ @dataclasses.dataclass(slots=True, kw_only=True) class SignalWithStartWorkflowRequest: - """ + """Request fields for signaling a workflow, starting it first if needed. + .. warning:: This API is experimental and subject to change. """ @@ -223,7 +224,8 @@ def to_transfer_type( @dataclasses.dataclass(slots=True) class UserMetadata: - """ + """Static metadata for a workflow execution. + .. warning:: This API is experimental and subject to change. """ @@ -276,7 +278,8 @@ def to_transfer_type( @dataclasses.dataclass(slots=True) class SignalWithStartWorkflowResponse: - """ + """Result of signaling a workflow and starting it if needed. + .. warning:: This API is experimental and subject to change. """ diff --git a/temporalio/nexus/system/workflow_service/operations/__init__.py b/temporalio/nexus/system/workflow_service/operations/__init__.py index ff561b55c0..0dd909df4d 100644 --- a/temporalio/nexus/system/workflow_service/operations/__init__.py +++ b/temporalio/nexus/system/workflow_service/operations/__init__.py @@ -1,3 +1,3 @@ -# Generated by nexgen v0.2.8. DO NOT EDIT! +# Generated by nexgen v0.2.10. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/operations/signal_with_start_workflow.py b/temporalio/nexus/system/workflow_service/operations/signal_with_start_workflow.py index 28b4735436..51af0c06a8 100644 --- a/temporalio/nexus/system/workflow_service/operations/signal_with_start_workflow.py +++ b/temporalio/nexus/system/workflow_service/operations/signal_with_start_workflow.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.8. DO NOT EDIT! +# Generated by nexgen v0.2.10. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/services.py b/temporalio/nexus/system/workflow_service/services.py index c95fff35ad..a1e379f117 100644 --- a/temporalio/nexus/system/workflow_service/services.py +++ b/temporalio/nexus/system/workflow_service/services.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.8. DO NOT EDIT! +# Generated by nexgen v0.2.10. DO NOT EDIT! from __future__ import annotations diff --git a/tests/nexus/test_system_nexus_fallthrough.py b/tests/nexus/test_system_nexus_fallthrough.py new file mode 100644 index 0000000000..f712f9ab2e --- /dev/null +++ b/tests/nexus/test_system_nexus_fallthrough.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import uuid +from collections.abc import Generator +from datetime import timedelta +from typing import Any + +from temporalio import workflow +from temporalio.client import Client +from temporalio.nexus.system import TEMPORAL_SYSTEM_ENDPOINT +from temporalio.worker import ( + Interceptor, + StartNexusOperationInput, + WorkflowInboundInterceptor, + WorkflowInterceptorClassInput, + WorkflowOutboundInterceptor, +) +from tests.helpers import new_worker + +PLUGIN_SERVICE = "test.plugin.v1.EchoService" + + +class _DoneHandle(workflow.NexusOperationHandle[Any]): + def __init__(self, value: Any) -> None: + self._value = value + + def cancel(self) -> bool: + return False + + def __await__(self) -> Generator[Any, Any, Any]: + return self._result().__await__() + + async def _result(self) -> Any: + return self._value + + @property + def operation_token(self) -> str | None: + return None + + +class _EchoOutboundInterceptor(WorkflowOutboundInterceptor): + async def start_system_nexus_operation( + self, input: StartNexusOperationInput[Any, Any] + ) -> workflow.NexusOperationHandle[Any]: + if input.service == PLUGIN_SERVICE: + return _DoneHandle(f"{input.operation_name}: {input.input}") + return await super().start_system_nexus_operation(input) + + +class _EchoInboundInterceptor(WorkflowInboundInterceptor): + def init(self, outbound: WorkflowOutboundInterceptor) -> None: + super().init(_EchoOutboundInterceptor(outbound)) + + +class EchoServiceInterceptor(Interceptor): + def workflow_interceptor_class( + self, input: WorkflowInterceptorClassInput + ) -> type[WorkflowInboundInterceptor] | None: + return _EchoInboundInterceptor + + +@workflow.defn +class PluginServiceWorkflow: + @workflow.run + async def run(self) -> str: + client = workflow.create_nexus_client( + service=PLUGIN_SERVICE, endpoint=TEMPORAL_SYSTEM_ENDPOINT + ) + handle: workflow.NexusOperationHandle[Any] = await client.start_operation( + "Echo", "hello" + ) + return await handle + + +async def test_unknown_system_nexus_operation_reaches_generic_hook(client: Client): + async with new_worker( + client, PluginServiceWorkflow, interceptors=[EchoServiceInterceptor()] + ) as worker: + result = await client.execute_workflow( + PluginServiceWorkflow.run, + id=f"wf-{uuid.uuid4()}", + task_queue=worker.task_queue, + execution_timeout=timedelta(seconds=10), + ) + assert result == "Echo: hello" From f20df4203539e9d246491198dda52f0bbeb6c600 Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Wed, 21 Oct 2026 13:06:09 -0700 Subject: [PATCH 2/3] Pinned nexgen 0.2.11, the first release with the fall-through. nexgen 0.2.10 is already released without the fall-through, so check-protos would install it and get other output. --- .github/workflows/ci.yml | 2 +- scripts/gen_nexus_system_api.py | 2 +- temporalio/nexus/notifications/__init__.py | 2 +- temporalio/nexus/notifications/models.py | 2 +- temporalio/nexus/system/workflow_service/__init__.py | 2 +- .../nexus/system/workflow_service/_system_nexus_interceptor.py | 2 +- temporalio/nexus/system/workflow_service/models.py | 2 +- temporalio/nexus/system/workflow_service/operations/__init__.py | 2 +- .../workflow_service/operations/signal_with_start_workflow.py | 2 +- temporalio/nexus/system/workflow_service/services.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb5a1653f6..c7a0065a77 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -229,7 +229,7 @@ jobs: - run: uv add --dev --python 3.10 "googleapis-common-protos==1.70.0" - run: uv add --python 3.10 "protobuf<4" - run: uv sync --all-extras - - run: cargo install --locked nexgen --version 0.2.10 --features advanced --force + - run: cargo install --locked nexgen --version 0.2.11 --features advanced --force - run: poe build-develop - run: poe gen-protos - name: Check generation unchanged diff --git a/scripts/gen_nexus_system_api.py b/scripts/gen_nexus_system_api.py index c2a9deda60..c37df01812 100644 --- a/scripts/gen_nexus_system_api.py +++ b/scripts/gen_nexus_system_api.py @@ -25,7 +25,7 @@ workflow_output_dir = base_dir / "temporalio" / "nexus" / "system" / "workflow_service" notification_output_dir = base_dir / "temporalio" / "nexus" / "notifications" workflow_init_path = base_dir / "temporalio" / "workflow" / "__init__.py" -NEX_GEN_VERSION = "0.2.10" +NEX_GEN_VERSION = "0.2.11" proto_files = [ gen_protos.api_proto_dir / "temporal" diff --git a/temporalio/nexus/notifications/__init__.py b/temporalio/nexus/notifications/__init__.py index d5ff1b8dac..6b8a5bdbba 100644 --- a/temporalio/nexus/notifications/__init__.py +++ b/temporalio/nexus/notifications/__init__.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.10. DO NOT EDIT! +# Generated by nexgen v0.2.11. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/notifications/models.py b/temporalio/nexus/notifications/models.py index a94cffaef0..ef5528a6f8 100644 --- a/temporalio/nexus/notifications/models.py +++ b/temporalio/nexus/notifications/models.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.10. DO NOT EDIT! +# Generated by nexgen v0.2.11. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/__init__.py b/temporalio/nexus/system/workflow_service/__init__.py index 84fb38a80f..1d3efca427 100644 --- a/temporalio/nexus/system/workflow_service/__init__.py +++ b/temporalio/nexus/system/workflow_service/__init__.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.10. DO NOT EDIT! +# Generated by nexgen v0.2.11. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/_system_nexus_interceptor.py b/temporalio/nexus/system/workflow_service/_system_nexus_interceptor.py index e5c058079e..c1e992c8c7 100644 --- a/temporalio/nexus/system/workflow_service/_system_nexus_interceptor.py +++ b/temporalio/nexus/system/workflow_service/_system_nexus_interceptor.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.10. DO NOT EDIT! +# Generated by nexgen v0.2.11. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/models.py b/temporalio/nexus/system/workflow_service/models.py index 87efea1abe..8fa35902fc 100644 --- a/temporalio/nexus/system/workflow_service/models.py +++ b/temporalio/nexus/system/workflow_service/models.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.10. DO NOT EDIT! +# Generated by nexgen v0.2.11. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/operations/__init__.py b/temporalio/nexus/system/workflow_service/operations/__init__.py index 0dd909df4d..3bd09999cf 100644 --- a/temporalio/nexus/system/workflow_service/operations/__init__.py +++ b/temporalio/nexus/system/workflow_service/operations/__init__.py @@ -1,3 +1,3 @@ -# Generated by nexgen v0.2.10. DO NOT EDIT! +# Generated by nexgen v0.2.11. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/operations/signal_with_start_workflow.py b/temporalio/nexus/system/workflow_service/operations/signal_with_start_workflow.py index 51af0c06a8..057c6078ed 100644 --- a/temporalio/nexus/system/workflow_service/operations/signal_with_start_workflow.py +++ b/temporalio/nexus/system/workflow_service/operations/signal_with_start_workflow.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.10. DO NOT EDIT! +# Generated by nexgen v0.2.11. DO NOT EDIT! from __future__ import annotations diff --git a/temporalio/nexus/system/workflow_service/services.py b/temporalio/nexus/system/workflow_service/services.py index a1e379f117..61cf577bbe 100644 --- a/temporalio/nexus/system/workflow_service/services.py +++ b/temporalio/nexus/system/workflow_service/services.py @@ -1,4 +1,4 @@ -# Generated by nexgen v0.2.10. DO NOT EDIT! +# Generated by nexgen v0.2.11. DO NOT EDIT! from __future__ import annotations From 0e7ed35ae26fef885a526e230f84604c91f043a1 Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Wed, 21 Oct 2026 22:24:17 -0700 Subject: [PATCH 3/3] Added a changelog entry for the System Nexus fall-through. --- changelog/fixed/wandering-walrus.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/fixed/wandering-walrus.md diff --git a/changelog/fixed/wandering-walrus.md b/changelog/fixed/wandering-walrus.md new file mode 100644 index 0000000000..973f00c09c --- /dev/null +++ b/changelog/fixed/wandering-walrus.md @@ -0,0 +1 @@ +System Nexus operations with no generated interceptor now go to the generic `start_system_nexus_operation` hook of the Workflow outbound interceptors, instead of raising.