From e5997669454d20955528c56435cd76b55a6c3853 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Mon, 1 Jun 2026 10:50:39 -0500 Subject: [PATCH 1/2] feat: add system Nexus operation registry to proxy package Export SystemNexusOperations, a canonical mapping of known system Nexus operations to their request/response proto types. This allows consumers (CLI, cloud-cli, SDKs) to decode payloads nested inside opaque system Nexus operation bytes without maintaining their own registries. --- proxy/nested_payload.go | 39 ++++++++++++++++++++++++++++++++++++ proxy/nested_payload_test.go | 25 +++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 proxy/nested_payload.go create mode 100644 proxy/nested_payload_test.go diff --git a/proxy/nested_payload.go b/proxy/nested_payload.go new file mode 100644 index 00000000..2745b4fe --- /dev/null +++ b/proxy/nested_payload.go @@ -0,0 +1,39 @@ +package proxy + +import ( + workflowservice "go.temporal.io/api/workflowservice/v1" + "google.golang.org/protobuf/proto" +) + +const ( + // TemporalSystemNexusEndpoint is the well-known endpoint name for system Nexus operations. + TemporalSystemNexusEndpoint = "__temporal_system" +) + +// SystemNexusOpKey identifies a system Nexus operation by its (endpoint, operation) pair. +type SystemNexusOpKey struct { + Endpoint string + Operation string +} + +// SystemNexusOpTypes maps a system Nexus operation to the proto request and response +// types whose bytes are serialized in NexusOperationScheduled.Input and +// NexusOperationCompleted.Result. +type SystemNexusOpTypes struct { + // NewRequest returns a fresh, zero-valued instance of the request proto. + NewRequest func() proto.Message + // NewResponse returns a fresh, zero-valued instance of the response proto. + NewResponse func() proto.Message +} + +// SystemNexusOperations is the canonical registry of known system Nexus operations. +// Consumers should use this rather than maintaining their own mapping. +var SystemNexusOperations = map[SystemNexusOpKey]SystemNexusOpTypes{ + { + Endpoint: TemporalSystemNexusEndpoint, + Operation: "SignalWithStartWorkflowExecution", + }: { + NewRequest: func() proto.Message { return &workflowservice.SignalWithStartWorkflowExecutionRequest{} }, + NewResponse: func() proto.Message { return &workflowservice.SignalWithStartWorkflowExecutionResponse{} }, + }, +} diff --git a/proxy/nested_payload_test.go b/proxy/nested_payload_test.go new file mode 100644 index 00000000..7c2db406 --- /dev/null +++ b/proxy/nested_payload_test.go @@ -0,0 +1,25 @@ +package proxy + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.temporal.io/api/workflowservice/v1" +) + +func TestSystemNexusOperations_SignalWithStartRegistered(t *testing.T) { + key := SystemNexusOpKey{ + Endpoint: TemporalSystemNexusEndpoint, + Operation: "SignalWithStartWorkflowExecution", + } + types, ok := SystemNexusOperations[key] + require.True(t, ok, "SignalWithStartWorkflowExecution must be registered") + + req := types.NewRequest() + _, ok = req.(*workflowservice.SignalWithStartWorkflowExecutionRequest) + require.True(t, ok, "request type must be SignalWithStartWorkflowExecutionRequest") + + resp := types.NewResponse() + _, ok = resp.(*workflowservice.SignalWithStartWorkflowExecutionResponse) + require.True(t, ok, "response type must be SignalWithStartWorkflowExecutionResponse") +} From 50350b914bb1fee4a65b8efa88ec364742b600cd Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:32:06 -0500 Subject: [PATCH 2/2] feat: build system Nexus operation registry dynamically from generated code Use reflection to iterate the generated workflowservicenexus.WorkflowService struct fields, building the SystemNexusOperations registry automatically. New operations added to temporalio/api are picked up without changes to this repo or downstream consumers. --- proxy/nested_payload.go | 62 ++++++++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 10 deletions(-) diff --git a/proxy/nested_payload.go b/proxy/nested_payload.go index 2745b4fe..737c460e 100644 --- a/proxy/nested_payload.go +++ b/proxy/nested_payload.go @@ -1,7 +1,9 @@ package proxy import ( - workflowservice "go.temporal.io/api/workflowservice/v1" + "reflect" + + "go.temporal.io/api/workflowservice/v1/workflowservicenexus" "google.golang.org/protobuf/proto" ) @@ -27,13 +29,53 @@ type SystemNexusOpTypes struct { } // SystemNexusOperations is the canonical registry of known system Nexus operations. -// Consumers should use this rather than maintaining their own mapping. -var SystemNexusOperations = map[SystemNexusOpKey]SystemNexusOpTypes{ - { - Endpoint: TemporalSystemNexusEndpoint, - Operation: "SignalWithStartWorkflowExecution", - }: { - NewRequest: func() proto.Message { return &workflowservice.SignalWithStartWorkflowExecutionRequest{} }, - NewResponse: func() proto.Message { return &workflowservice.SignalWithStartWorkflowExecutionResponse{} }, - }, +// It is built dynamically from the generated workflowservicenexus package so that +// new operations added to the proto definitions are picked up automatically without +// requiring changes to this file or downstream consumers. +var SystemNexusOperations = buildNexusServiceRegistry( + TemporalSystemNexusEndpoint, + workflowservicenexus.WorkflowService, +) + +// buildNexusServiceRegistry uses reflection to iterate the fields of a generated +// Nexus service struct (e.g. workflowservicenexus.WorkflowService) and builds a +// registry entry for each field that implements the nexus.OperationReference +// interface (i.e. has Name(), InputType(), and OutputType() methods). +func buildNexusServiceRegistry(endpoint string, service any) map[SystemNexusOpKey]SystemNexusOpTypes { + registry := make(map[SystemNexusOpKey]SystemNexusOpTypes) + v := reflect.ValueOf(service) + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + nameMethod := field.MethodByName("Name") + if !nameMethod.IsValid() { + continue + } + inputTypeMethod := field.MethodByName("InputType") + outputTypeMethod := field.MethodByName("OutputType") + if !inputTypeMethod.IsValid() || !outputTypeMethod.IsValid() { + continue + } + + name := nameMethod.Call(nil)[0].String() + inputType := inputTypeMethod.Call(nil)[0].Interface().(reflect.Type) + outputType := outputTypeMethod.Call(nil)[0].Interface().(reflect.Type) + + registry[SystemNexusOpKey{Endpoint: endpoint, Operation: name}] = SystemNexusOpTypes{ + NewRequest: newProtoFactory(inputType), + NewResponse: newProtoFactory(outputType), + } + } + return registry +} + +// newProtoFactory returns a function that creates a new zero-valued proto.Message +// of the given type. The type should be a struct type (not a pointer); the returned +// function allocates a new instance and returns a pointer to it as proto.Message. +func newProtoFactory(t reflect.Type) func() proto.Message { + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return func() proto.Message { + return reflect.New(t).Interface().(proto.Message) + } }