From 037dc8d3bf620fa923bc2ded667530bac3310e7b Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Sat, 17 Oct 2026 01:34:21 -0700 Subject: [PATCH 1/5] Vendored the Nexus handler callback's progress operation field. --- .../protos/api_upstream/openapi/openapiv2.json | 6 +++++- .../protos/api_upstream/openapi/openapiv3.yaml | 12 ++++++++++++ .../temporal/api/common/v1/message.proto | 11 +++++++++++ crates/protos/src/protos/mod.rs | 16 ++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/crates/protos/protos/api_upstream/openapi/openapiv2.json b/crates/protos/protos/api_upstream/openapi/openapiv2.json index c17eeac00e..8c1d27ea61 100644 --- a/crates/protos/protos/api_upstream/openapi/openapiv2.json +++ b/crates/protos/protos/api_upstream/openapi/openapiv2.json @@ -10879,9 +10879,13 @@ "$ref": "#/definitions/v1Payload", "description": "There are restrictions on the maxium payload size a single callback can carry, as well as the\ntotal sum of all source context payloads attached to an execution. See dynamic configuration:\n\"callback.nexusHandler.sourceContext.maxSize\", \"callback.nexusHandler.sourceContext.aggregateMaxSize\".", "title": "Arbitrary user-supplied data from the source operation's callsite. (As applicable, not all operations\nsupport attaching context data.)" + }, + "progressOperation": { + "type": "string", + "description": "Target operation for progress of the source operation, e.g. \"OnProgress\". Empty means the\ncallback takes the completion only." } }, - "description": "The targeted Nexus service must be registered within the same namespace as the source operation\nthe callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks\nare strictly caller-side.)\n\nNexusHandler callbacks are only supported for certain types of operations, e.g. standalone Nexus operations.\nAttempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT\nerror from the server.", + "description": "When `progress_operation` is set, the server also invokes that operation of the same service for\nthe source operation's progress. That operation must have the following:\n- Input: temporal.api.notificationservice.v1.OnProgressRequest\n- Output: temporal.api.notificationservice.v1.OnProgressResponse\nIt may be invoked any number of times before, during or after the single completion. It must\ncomplete synchronously.\n\nThe targeted Nexus service must be registered within the same namespace as the source operation\nthe callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks\nare strictly caller-side.)\n\nNexusHandler callbacks are only supported for certain types of operations, e.g. standalone Nexus operations.\nAttempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT\nerror from the server.", "title": "NexusHandler callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker.\nThe specified Nexus operation must have the following:\n- Input: temporal.api.notificationservice.v1.OnCompleteRequest\n- Output: temporal.api.notificationservice.v1.OnCompleteResponse" }, "ComputeStatusProviderValidationStatus": { diff --git a/crates/protos/protos/api_upstream/openapi/openapiv3.yaml b/crates/protos/protos/api_upstream/openapi/openapiv3.yaml index d599bcce7f..3d9caa4d34 100644 --- a/crates/protos/protos/api_upstream/openapi/openapiv3.yaml +++ b/crates/protos/protos/api_upstream/openapi/openapiv3.yaml @@ -10935,12 +10935,24 @@ components: There are restrictions on the maxium payload size a single callback can carry, as well as the total sum of all source context payloads attached to an execution. See dynamic configuration: "callback.nexusHandler.sourceContext.maxSize", "callback.nexusHandler.sourceContext.aggregateMaxSize". + progressOperation: + type: string + description: |- + Target operation for progress of the source operation, e.g. "OnProgress". Empty means the + callback takes the completion only. description: |- NexusHandler callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker. The specified Nexus operation must have the following: - Input: temporal.api.notificationservice.v1.OnCompleteRequest - Output: temporal.api.notificationservice.v1.OnCompleteResponse + When `progress_operation` is set, the server also invokes that operation of the same service for + the source operation's progress. That operation must have the following: + - Input: temporal.api.notificationservice.v1.OnProgressRequest + - Output: temporal.api.notificationservice.v1.OnProgressResponse + It may be invoked any number of times before, during or after the single completion. It must + complete synchronously. + The targeted Nexus service must be registered within the same namespace as the source operation the callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks are strictly caller-side.) diff --git a/crates/protos/protos/api_upstream/temporal/api/common/v1/message.proto b/crates/protos/protos/api_upstream/temporal/api/common/v1/message.proto index 908c5c80b8..95256a4ddf 100644 --- a/crates/protos/protos/api_upstream/temporal/api/common/v1/message.proto +++ b/crates/protos/protos/api_upstream/temporal/api/common/v1/message.proto @@ -207,6 +207,13 @@ message Callback { // - Input: temporal.api.notificationservice.v1.OnCompleteRequest // - Output: temporal.api.notificationservice.v1.OnCompleteResponse // + // When `progress_operation` is set, the server also invokes that operation of the same service for + // the source operation's progress. That operation must have the following: + // - Input: temporal.api.notificationservice.v1.OnProgressRequest + // - Output: temporal.api.notificationservice.v1.OnProgressResponse + // It may be invoked any number of times before, during or after the single completion. It must + // complete synchronously. + // // The targeted Nexus service must be registered within the same namespace as the source operation // the callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks // are strictly caller-side.) @@ -232,6 +239,10 @@ message Callback { // total sum of all source context payloads attached to an execution. See dynamic configuration: // "callback.nexusHandler.sourceContext.maxSize", "callback.nexusHandler.sourceContext.aggregateMaxSize". temporal.api.common.v1.Payload source_context = 4; + + // Target operation for progress of the source operation, e.g. "OnProgress". Empty means the + // callback takes the completion only. + string progress_operation = 5; } reserved 1; // For a generic callback mechanism to be added later. diff --git a/crates/protos/src/protos/mod.rs b/crates/protos/src/protos/mod.rs index db205fb3c9..1c5972cbbe 100644 --- a/crates/protos/src/protos/mod.rs +++ b/crates/protos/src/protos/mod.rs @@ -3043,6 +3043,22 @@ mod sdk_helpers { }; use anyhow::anyhow; + mod nexus_handler_callback { + use crate::protos::temporal::api::common::v1::callback::NexusHandler; + use prost::Message; + + // The server reads the progress operation by its field number, so a renumbered field + // would send progress to an operation the Worker never named. + #[test] + fn the_progress_operation_keeps_its_field_number() { + let handler = NexusHandler { + progress_operation: "p".to_string(), + ..Default::default() + }; + assert_eq!(handler.encode_to_vec(), vec![0x2a, 0x01, b'p']); + } + } + #[test] fn start_from_poll_resp_standalone_activity_populates_run_id() { let resp = PollActivityTaskQueueResponse { From 33f5f2039035b0a3ed35329f185c303a3c53df4c Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Sat, 17 Oct 2026 02:22:38 -0700 Subject: [PATCH 2/5] Vendored the completion request's receiver state field. --- .../v1/request_response.proto | 4 ++++ crates/protos/src/protos/mod.rs | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/crates/protos/protos/api_upstream/temporal/api/notificationservice/v1/request_response.proto b/crates/protos/protos/api_upstream/temporal/api/notificationservice/v1/request_response.proto index 9a7ac529f2..0561d888e8 100644 --- a/crates/protos/protos/api_upstream/temporal/api/notificationservice/v1/request_response.proto +++ b/crates/protos/protos/api_upstream/temporal/api/notificationservice/v1/request_response.proto @@ -31,6 +31,10 @@ message OnCompleteRequest { // User-supplied data which was added to the source invocation. (As applicable.) temporal.api.common.v1.Payload source_context = 3; + + // The `receiver_state` of the last OnProgressResponse that the server accepted for this + // callback. Empty when no progress was accepted. + bytes receiver_state = 4; } // OnCompleteResponse is the return type of the OnComplete operation. diff --git a/crates/protos/src/protos/mod.rs b/crates/protos/src/protos/mod.rs index 1c5972cbbe..5f004ed666 100644 --- a/crates/protos/src/protos/mod.rs +++ b/crates/protos/src/protos/mod.rs @@ -3059,6 +3059,25 @@ mod sdk_helpers { } } + mod on_complete_request { + // The server writes the receiver state by its field number, so a renumbered field + // would hand the completion handler no state, or another field's bytes. Core builds no + // type for the notification service, so the test reads the vendored proto. + #[test] + fn the_receiver_state_keeps_its_field_number() { + let proto = include_str!( + "../../protos/api_upstream/temporal/api/notificationservice/v1/\ + request_response.proto" + ); + let message = proto + .split("message OnCompleteRequest {") + .nth(1) + .and_then(|rest| rest.split("\n}").next()) + .expect("the proto declares OnCompleteRequest"); + assert!(message.contains("bytes receiver_state = 4;"), "{message}"); + } + } + #[test] fn start_from_poll_resp_standalone_activity_populates_run_id() { let resp = PollActivityTaskQueueResponse { From c60925df67b420345a28e6a418e8b53f39c2fc2b Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Sat, 17 Oct 2026 21:28:47 -0700 Subject: [PATCH 3/5] Removed the two tests that only checked the vendored proto copy. Vendored protos are copied verbatim, so the tests checked the copy and not the server. --- crates/protos/src/protos/mod.rs | 35 --------------------------------- 1 file changed, 35 deletions(-) diff --git a/crates/protos/src/protos/mod.rs b/crates/protos/src/protos/mod.rs index 5f004ed666..db205fb3c9 100644 --- a/crates/protos/src/protos/mod.rs +++ b/crates/protos/src/protos/mod.rs @@ -3043,41 +3043,6 @@ mod sdk_helpers { }; use anyhow::anyhow; - mod nexus_handler_callback { - use crate::protos::temporal::api::common::v1::callback::NexusHandler; - use prost::Message; - - // The server reads the progress operation by its field number, so a renumbered field - // would send progress to an operation the Worker never named. - #[test] - fn the_progress_operation_keeps_its_field_number() { - let handler = NexusHandler { - progress_operation: "p".to_string(), - ..Default::default() - }; - assert_eq!(handler.encode_to_vec(), vec![0x2a, 0x01, b'p']); - } - } - - mod on_complete_request { - // The server writes the receiver state by its field number, so a renumbered field - // would hand the completion handler no state, or another field's bytes. Core builds no - // type for the notification service, so the test reads the vendored proto. - #[test] - fn the_receiver_state_keeps_its_field_number() { - let proto = include_str!( - "../../protos/api_upstream/temporal/api/notificationservice/v1/\ - request_response.proto" - ); - let message = proto - .split("message OnCompleteRequest {") - .nth(1) - .and_then(|rest| rest.split("\n}").next()) - .expect("the proto declares OnCompleteRequest"); - assert!(message.contains("bytes receiver_state = 4;"), "{message}"); - } - } - #[test] fn start_from_poll_resp_standalone_activity_populates_run_id() { let resp = PollActivityTaskQueueResponse { From 84dc65fdc137ac3b78db1389bb1bad9ea82b2715 Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Sat, 17 Oct 2026 21:34:01 -0700 Subject: [PATCH 4/5] Vendored the progress operation comment rewrap from api. --- crates/protos/protos/api_upstream/openapi/openapiv2.json | 2 +- crates/protos/protos/api_upstream/openapi/openapiv3.yaml | 8 ++++---- .../api_upstream/temporal/api/common/v1/message.proto | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/protos/protos/api_upstream/openapi/openapiv2.json b/crates/protos/protos/api_upstream/openapi/openapiv2.json index 8c1d27ea61..94d75239e7 100644 --- a/crates/protos/protos/api_upstream/openapi/openapiv2.json +++ b/crates/protos/protos/api_upstream/openapi/openapiv2.json @@ -10885,7 +10885,7 @@ "description": "Target operation for progress of the source operation, e.g. \"OnProgress\". Empty means the\ncallback takes the completion only." } }, - "description": "When `progress_operation` is set, the server also invokes that operation of the same service for\nthe source operation's progress. That operation must have the following:\n- Input: temporal.api.notificationservice.v1.OnProgressRequest\n- Output: temporal.api.notificationservice.v1.OnProgressResponse\nIt may be invoked any number of times before, during or after the single completion. It must\ncomplete synchronously.\n\nThe targeted Nexus service must be registered within the same namespace as the source operation\nthe callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks\nare strictly caller-side.)\n\nNexusHandler callbacks are only supported for certain types of operations, e.g. standalone Nexus operations.\nAttempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT\nerror from the server.", + "description": "When `progress_operation` is set, the server also invokes that operation of the same service\nfor the source operation's progress. That operation must have the following:\n- Input: temporal.api.notificationservice.v1.OnProgressRequest\n- Output: temporal.api.notificationservice.v1.OnProgressResponse\nIt may be invoked any number of times before, during or after the single completion. It\nmust complete synchronously.\n\nThe targeted Nexus service must be registered within the same namespace as the source operation\nthe callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks\nare strictly caller-side.)\n\nNexusHandler callbacks are only supported for certain types of operations, e.g. standalone Nexus operations.\nAttempting to attach a Worker callback for an unsupported operation will result in an INVALID_ARGUMENT\nerror from the server.", "title": "NexusHandler callbacks are requests to invoke a specific shape of Nexus operation on a Temporal worker.\nThe specified Nexus operation must have the following:\n- Input: temporal.api.notificationservice.v1.OnCompleteRequest\n- Output: temporal.api.notificationservice.v1.OnCompleteResponse" }, "ComputeStatusProviderValidationStatus": { diff --git a/crates/protos/protos/api_upstream/openapi/openapiv3.yaml b/crates/protos/protos/api_upstream/openapi/openapiv3.yaml index 3d9caa4d34..41ecba7c19 100644 --- a/crates/protos/protos/api_upstream/openapi/openapiv3.yaml +++ b/crates/protos/protos/api_upstream/openapi/openapiv3.yaml @@ -10946,12 +10946,12 @@ components: - Input: temporal.api.notificationservice.v1.OnCompleteRequest - Output: temporal.api.notificationservice.v1.OnCompleteResponse - When `progress_operation` is set, the server also invokes that operation of the same service for - the source operation's progress. That operation must have the following: + When `progress_operation` is set, the server also invokes that operation of the same service + for the source operation's progress. That operation must have the following: - Input: temporal.api.notificationservice.v1.OnProgressRequest - Output: temporal.api.notificationservice.v1.OnProgressResponse - It may be invoked any number of times before, during or after the single completion. It must - complete synchronously. + It may be invoked any number of times before, during or after the single completion. It + must complete synchronously. The targeted Nexus service must be registered within the same namespace as the source operation the callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks diff --git a/crates/protos/protos/api_upstream/temporal/api/common/v1/message.proto b/crates/protos/protos/api_upstream/temporal/api/common/v1/message.proto index 95256a4ddf..14aca95b68 100644 --- a/crates/protos/protos/api_upstream/temporal/api/common/v1/message.proto +++ b/crates/protos/protos/api_upstream/temporal/api/common/v1/message.proto @@ -207,12 +207,12 @@ message Callback { // - Input: temporal.api.notificationservice.v1.OnCompleteRequest // - Output: temporal.api.notificationservice.v1.OnCompleteResponse // - // When `progress_operation` is set, the server also invokes that operation of the same service for - // the source operation's progress. That operation must have the following: + // When `progress_operation` is set, the server also invokes that operation of the same service + // for the source operation's progress. That operation must have the following: // - Input: temporal.api.notificationservice.v1.OnProgressRequest // - Output: temporal.api.notificationservice.v1.OnProgressResponse - // It may be invoked any number of times before, during or after the single completion. It must - // complete synchronously. + // It may be invoked any number of times before, during or after the single completion. It + // must complete synchronously. // // The targeted Nexus service must be registered within the same namespace as the source operation // the callback is attached to. (While Nexus allows for cross-namespace operations, NexusHandler callbacks From 76a64b43ff06d0d9bb4e3ca76f83ff5be893d283 Mon Sep 17 00:00:00 2001 From: Mohammad Dashti Date: Sun, 18 Oct 2026 00:03:58 -0700 Subject: [PATCH 5/5] Added a changelog entry for the Nexus callback fields. --- crates/sdk-core/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/sdk-core/CHANGELOG.md b/crates/sdk-core/CHANGELOG.md index 379940889f..bed6451e6a 100644 --- a/crates/sdk-core/CHANGELOG.md +++ b/crates/sdk-core/CHANGELOG.md @@ -42,6 +42,8 @@ relevant information. * Lang can set `heartbeat_task` on a successful activation completion to complete the workflow task while local activities still run, as Core does at a local activity heartbeat. Core ignores the flag on replay. +* Core keeps `progress_operation` on the Nexus callbacks it decodes from lang, and its vendored + protos include `receiver_state` on the Nexus `OnCompleteRequest`. ### Breaking Changes * Replay detects changes to the service or operation of scheduled Nexus operations, reporting