Populate resource_id fields for request header routing#2226
Populate resource_id fields for request header routing#2226tconley1428 wants to merge 18 commits intomasterfrom
Conversation
Implement logic to populate resource_id fields in request messages to support multi-cell request routing based on resource IDs. Changes: - Workflow task requests: Populate with workflow ID from task context - Activity task requests: Use workflow ID for workflow activities, activity ID for standalone activities - Activity heartbeat requests: Extract resource ID from activity context - Batch operation requests: Use workflow ID from start operation - Worker heartbeat requests: Use worker grouping key Added helper functions: - getActivityResourceId(workflowId, activityId): Determines appropriate resource ID - getActivityResourceIdFromCtx(ctx): Extracts resource ID from activity context All resource_id fields are now properly populated to enable correct header extraction and routing via the temporal-resource-id header. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Implement resource_id field population for Nexus task completion and failure requests. Changes: - Modified fillInCompletion() to accept resourceId parameter and populate RespondNexusTaskCompletedRequest.resource_id - Modified fillInFailure() to accept resourceId parameter and populate RespondNexusTaskFailedRequest.resource_id - Updated all calls to pass task.ResourceId from PollNexusTaskQueueResponse The resource_id field is now populated with the value from PollNexusTaskQueueResponse.resource_id as specified in the proto comments, enabling proper request routing via temporal-resource-id headers. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
This commit adds tests for all 8 activity task resource ID fields to ensure proper resource_id population for multi-cell routing. Key additions: - Comprehensive test suite in resource_id_impl_test.go covering: * All 3 workflow task requests (completed, failed, query) * All 8 activity task requests using conversion function validation * Both workflow and standalone activity scenarios - Uses convertActivityResultToRespondRequest and convertActivityResultToRespondRequestByID as validation points to test resource_id field population - Tests cover RespondActivityTaskCompleted/Failed/Canceled and ByID variants - Updated RESOURCE_ID_FIELDS.md with detailed testing status (10/15 tested, 66.7% coverage) All tests pass and validate that: - Workflow ID is used when present - Activity ID is used for standalone activities (empty workflow ID) - Conversion functions properly populate resource_id fields via getActivityResourceId helper 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Add comprehensive test for RecordActivityTaskHeartbeatByIdRequest resource_id field to move from partial to full test coverage. Changes: - Added testActivityTaskHeartbeatByIdResourceID function with proper mocking - Tests both workflow execution and standalone activity scenarios - Validates resource_id field is set to workflow ID when present, activity ID when standalone - Uses RecordActivityHeartbeatByID client method with proper service mocking - Updated RESOURCE_ID_FIELDS.md to reflect full test coverage (11/15 tested, 73.3%) This completes testing for all 8 activity task resource ID fields, bringing activity task test coverage to 100% (8/8 tested). 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Remove resource_id field implementations and tests for messages that no longer have resource_id fields in the proto definitions: - RespondQueryTaskCompletedRequest: Removed ResourceId assignment and test - RespondNexusTaskCompletedRequest: Removed ResourceId field and unused resourceId parameter - RespondNexusTaskFailedRequest: Removed ResourceId field and unused resourceId parameter Also add comprehensive test for ExecuteMultiOperationRequest resource_id field, bringing test coverage to 91.7% (11/12 implemented fields tested). Changes: - Remove ResourceId assignments in task handlers and pollers - Remove unused resourceId parameters from fillInCompletion() and fillInFailure() - Add testExecuteMultiOperationResourceID() with UpdateWithStartWorkflow validation - Update RESOURCE_ID_FIELDS.md to reflect current proto state and test coverage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
Verified that all 12 resource ID fields have proper test coverage by: - Temporarily removing ResourceId assignments - Confirming tests fail with empty ResourceId - Restoring ResourceId assignments - Verifying tests pass with ResourceId restored This ensures all resource ID tests will catch regressions where fields are not populated correctly during request routing. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
- Rename resource_id_impl_test.go to resource_id_test.go for conventional naming - Remove RESOURCE_ID_FIELDS.md working state file 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
_**READ BEFORE MERGING:** All PRs require approval by both Server AND SDK teams before merging! This is why the number of required approvals is "2" and not "1"--two reviewers from the same team is NOT sufficient. If your PR is not approved by someone in BOTH teams, it may be summarily reverted._ <!-- Describe what has changed in this PR --> Adding a proto annotation to be used for automatically propagating message fields into headers. Will be accompanied by SDK changes to generate code for doing so based on the annotations. See temporalio/api-go#236 and temporalio/sdk-go#2226 for example. Also adds additional `resource-id` fields to a number of messages for use in routing. <!-- Tell your future self why have you made these changes --> MCN support <!-- Are there any breaking changes on binary or code level? --> **Breaking changes** <!-- If this breaks the Server, please provide the Server PR to merge right after this PR was merged. --> **Server PR** --------- Co-authored-by: Claude <[email protected]>
_**READ BEFORE MERGING:** All PRs require approval by both Server AND SDK teams before merging! This is why the number of required approvals is "2" and not "1"--two reviewers from the same team is NOT sufficient. If your PR is not approved by someone in BOTH teams, it may be summarily reverted._ <!-- Describe what has changed in this PR --> Adding a proto annotation to be used for automatically propagating message fields into headers. Will be accompanied by SDK changes to generate code for doing so based on the annotations. See #236 and temporalio/sdk-go#2226 for example. Also adds additional `resource-id` fields to a number of messages for use in routing. <!-- Tell your future self why have you made these changes --> MCN support <!-- Are there any breaking changes on binary or code level? --> **Breaking changes** <!-- If this breaks the Server, please provide the Server PR to merge right after this PR was merged. --> **Server PR** --------- Co-authored-by: Claude <[email protected]>
_**READ BEFORE MERGING:** All PRs require approval by both Server AND SDK teams before merging! This is why the number of required approvals is "2" and not "1"--two reviewers from the same team is NOT sufficient. If your PR is not approved by someone in BOTH teams, it may be summarily reverted._ <!-- Describe what has changed in this PR --> Adding a proto annotation to be used for automatically propagating message fields into headers. Will be accompanied by SDK changes to generate code for doing so based on the annotations. See temporalio/api-go#236 and temporalio/sdk-go#2226 for example. Also adds additional `resource-id` fields to a number of messages for use in routing. <!-- Tell your future self why have you made these changes --> MCN support <!-- Are there any breaking changes on binary or code level? --> **Breaking changes** <!-- If this breaks the Server, please provide the Server PR to merge right after this PR was merged. --> **Server PR** --------- Co-authored-by: Claude <[email protected]>
- All resource_id fields now carry fully-prefixed values from the SDK (e.g., "workflow:id", "worker:key", "activity:id") - Consolidate getActivityResourceIdFromCtx to delegate to getActivityResourceId instead of duplicating prefix logic - Return empty string from getActivityResourceId when both IDs are empty - Update tests to expect prefixed values Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Upgrade to published api-go v1.62.6 which includes the request header proxy generator and standardized resource_id annotations. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Resolve merge conflicts in go.mod/go.sum (keep v1.62.6) and fix two callsites of errorToFailWorkflowTask that needed the workflowId param. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Remove unused createTestQueryTask helper and querypb import - Add TestGetActivityResourceId_BothEmpty for the edge case where both workflowId and activityId are empty Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Update go.temporal.io/api to v1.62.6 in all contrib and test modules. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
@claude review |
There was a problem hiding this comment.
The implementation looks systematic and has solid test coverage, but the scope is large enough (core task handlers, pollers, grpc interceptor refactor) that a human reviewer should sign off.
Extended reasoning...
Overview
This PR populates resource_id fields across multiple request types in the Temporal Go SDK to enable multi-cell request routing. It touches internal/grpc_dialer.go, internal_task_handlers.go, internal_task_pollers.go, internal_worker_heartbeat.go, internal_workflow_client.go, internal_workflow_testsuite.go, a new 825-line test file, and all module dependency files (go.temporal.io/api v1.62.5 to v1.62.6).
Security Risks
No direct security risks. The changes populate routing metadata headers, not authentication or authorization fields. The delegation to proxy.ExtractTemporalRequestHeaders in grpc_dialer.go replaces manual namespace header extraction; the new function receives ExistingMetadata to preserve the previous behavior of not overwriting existing headers, but this behavioral equivalence depends on the external library implementation which cannot be verified here.
Level of Scrutiny
This warrants human review. The PR touches several core internal components that handle task completion, activity heartbeats, and gRPC interceptors. The grpc_dialer.go change is particularly notable: it replaces a manually written interceptor with a call to an external library function (proxy.ExtractTemporalRequestHeaders), which is a behavioral change that deserves careful attention even if it is likely correct.
Other Factors
There is a resolved inline comment questioning whether workerGroupingKey (used in RecordWorkerHeartbeatRequest.ResourceId) is correct versus instanceKey. The comment is marked resolved but there is no follow-up explanation confirming which was chosen and why. A human reviewer familiar with the routing semantics should validate this choice. The CompleteActivity path passes empty strings for workflowId/activityId, resulting in an empty resource_id; this appears intentional (task-token-based completion lacks workflow context) but deserves explicit confirmation.
Resolve conflicts from upstream refactor of data/failure converter access patterns. Keep both: upstream's local variable references and our resource_id parameters. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
- Pass opts.WorkflowID to convertActivityResultToRespondRequest in CompleteActivityWithOptions so async completions include the temporal-resource-id header for proxy routing. ActivityID is not available on this code path (task-token based identification). - Use nil-safe getter GetWorkflowId() instead of direct field access on task.WorkflowExecution in completeWorkflow, consistent with all other usages in this file. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
| func recordActivityHeartbeat(ctx context.Context, service workflowservice.WorkflowServiceClient, metricsHandler metrics.Handler, | ||
| identity string, taskToken []byte, details *commonpb.Payloads, | ||
| ) error { | ||
| namespace := getNamespaceFromActivityCtx(ctx) | ||
| request := &workflowservice.RecordActivityTaskHeartbeatRequest{ | ||
| TaskToken: taskToken, | ||
| Details: details, | ||
| Identity: identity, | ||
| Namespace: namespace, | ||
| TaskToken: taskToken, | ||
| Details: details, | ||
| Identity: identity, | ||
| Namespace: namespace, | ||
| ResourceId: getActivityResourceIdFromCtx(ctx), | ||
| } |
There was a problem hiding this comment.
🔴 RecordActivityHeartbeatWithOptions in internal/internal_workflow_client.go never passes opts.WorkflowID to recordActivityHeartbeat, so RecordActivityTaskHeartbeatRequest.ResourceId is always empty when heartbeating from outside an activity — the exact primary use case for this API. This means proxy-based resource-id routing introduced by this PR will silently fail for all external/async heartbeat calls even when a WorkflowID is provided. The fix is to thread opts.WorkflowID into recordActivityHeartbeat (adding a workflowID parameter) and fall back to getActivityResourceId(workflowID, "") when the context has no activity environment, mirroring the pattern already used in recordActivityHeartbeatByID.
Extended reasoning...
Bug: RecordActivityHeartbeatWithOptions always produces empty ResourceId
What the bug is and how it manifests
RecordActivityHeartbeatWithOptions is the public API for workers (or application code) that hold an activity task token and want to report heartbeats outside the normal in-activity context — for example, in async or forked-process activity patterns. The caller provides opts.WorkflowID precisely so the system can associate the heartbeat with the correct workflow. PR #2226 adds ResourceId population to RecordActivityTaskHeartbeatRequest for proxy routing, but the RecordActivityHeartbeatWithOptions code path never sets ResourceId to anything other than empty string.
The specific code path that triggers it
RecordActivityHeartbeatWithOptions calls recordActivityHeartbeat(ctx, svc, metrics, identity, opts.TaskToken, data) — opts.WorkflowID is referenced only for ActivitySerializationContext and is not forwarded. Inside recordActivityHeartbeat, the request is built with ResourceId: getActivityResourceIdFromCtx(ctx). getActivityResourceIdFromCtx calls getActivityEnvironmentFromCtx(ctx), which returns nil for any context that did not go through the normal in-activity dispatch path (i.e., every external/async caller). When the env is nil, the helper immediately returns "", so ResourceId is always empty for this entire code path.
Why existing code does not prevent it
The same PR correctly fixes recordActivityHeartbeatByID by passing workflowID and activityID as explicit parameters and computing ResourceId: getActivityResourceId(workflowID, activityID). It also fixes CompleteActivityWithOptions to pass opts.WorkflowID. The heartbeat-with-options path was simply missed. There is no guard or fallback that would use opts.WorkflowID as a substitute when the context lookup returns nil.
Impact
Any deployment relying on the temporal-resource-id gRPC header (set by temporalHeaderInterceptor from ResourceId) for proxy routing will receive no header on RecordActivityTaskHeartbeat requests originating from RecordActivityHeartbeatWithOptions. Since this is the primary API for async activities, proxy routing silently fails for a significant class of requests. The omission is invisible at the Go API level — callers supply opts.WorkflowID and have no way to know it is being ignored.
How to fix it
Add a workflowID string parameter to recordActivityHeartbeat and compute ResourceId with a two-stage fallback: first try getActivityResourceIdFromCtx(ctx), then fall back to getActivityResourceId(workflowID, "") if empty. Then update RecordActivityHeartbeatWithOptions to pass opts.WorkflowID and all other internal callers to pass "". This mirrors the pattern already used in recordActivityHeartbeatByID.
Step-by-step proof
- A worker receives an activity task token, spawns a goroutine, and calls
client.RecordActivityHeartbeatWithOptions(ctx, opts)withopts.WorkflowID = "my-workflow-123"andopts.TaskToken = token. RecordActivityHeartbeatWithOptionsinvokesrecordActivityHeartbeat(ctx, svc, metrics, identity, opts.TaskToken, data)—opts.WorkflowIDis not forwarded.recordActivityHeartbeatbuilds the request withResourceId: getActivityResourceIdFromCtx(ctx).getActivityResourceIdFromCtxcallsgetActivityEnvironmentFromCtx(ctx)which returnsnil(external ctx has no activity env), so it returns"".- The outgoing
RecordActivityTaskHeartbeatRequesthasResourceId = "". temporalHeaderInterceptorcallsproxy.ExtractTemporalRequestHeaders, finds no resource id, and sets notemporal-resource-idheader.- The proxy cannot route the request to the correct cell, defeating the purpose of this PR for async heartbeats.
| Identity: wtp.identity, | ||
| BinaryChecksum: wtp.workerBuildID, | ||
| Namespace: wtp.namespace, | ||
| ResourceId: fmt.Sprintf("workflow:%s", workflowId), |
There was a problem hiding this comment.
🟡 Three locations in this PR use fmt.Sprintf("workflow:%s", workflowId) without guarding against an empty workflowId, inconsistent with the getActivityResourceId helper introduced in the same PR that correctly returns "" when both IDs are empty. While all three call sites are safe in practice due to server guarantees and upstream validation, adding a symmetric helper would improve defensive consistency.
Extended reasoning...
What the inconsistency is
This PR introduces (internal/internal_task_pollers.go ~line 1508) which correctly guards against empty strings:
However, three other locations in the same PR use the raw fmt.Sprintf pattern without guarding:
- internal/internal_task_pollers.go:782 (errorToFailWorkflowTaskWithCause): ResourceId: fmt.Sprintf("workflow:%s", workflowId)
- internal/internal_task_handlers.go:1975 (completeWorkflow): ResourceId: fmt.Sprintf("workflow:%s", task.WorkflowExecution.GetWorkflowId())
- internal/internal_workflow_client.go:2202 (updateWithStartWorkflow): ResourceId: fmt.Sprintf("workflow:%s", startRequest.WorkflowId)
If workflowId is ever empty at these sites, ResourceId becomes "workflow:" (a malformed prefix with no ID) rather than "" (empty, meaning no routing hint).
Why practical impact is limited
All three call sites are effectively safe in practice:
- Sites 1 and 2 operate on PollWorkflowTaskQueueResponse objects that come from the Temporal server, which always populates workflow execution IDs in polled tasks.
- Site 3 (updateWithStartWorkflow) is only called from UpdateWithStartWorkflow, which calls createStartWorkflowRequest first. That function explicitly validates workflowID != "" and returns an error before updateWithStartWorkflow is ever reached with an empty workflow ID.
Why it is still worth noting
The inconsistency creates a maintenance risk: the getActivityResourceId helper was clearly written with defensive empty-string handling in mind, but three parallel sites were written without it. If the call patterns change in the future (e.g., a new call site without upstream validation), it would be easy to miss that "workflow:" is malformed. The resource ID is used for proxy routing, so a malformed value could cause misrouting rather than a clean no-op.
How to fix it
The simplest fix is to extract a getWorkflowResourceId helper analogous to getActivityResourceId, or extend the existing helper to be used at all three sites:
Step-by-step proof of the malformed output
- Suppose (hypothetically) that task.WorkflowExecution.GetWorkflowId() returns "" at line 1975 in completeWorkflow.
- fmt.Sprintf("workflow:%s", "") evaluates to the string "workflow:".
- This value is set as ResourceId in the RespondWorkflowTaskCompletedRequest.
- The temporalHeaderInterceptor calls proxy.ExtractTemporalRequestHeaders which reads this ResourceId field and emits it as the temporal-resource-id gRPC header value "workflow:".
- The proxy/load-balancer receives temporal-resource-id: workflow: and attempts to route based on a workflow ID of "" -- which is meaningless and could cause a routing error or misroute to a default cell.
- The correct behavior would be to omit the header entirely (empty ResourceId), which the getActivityResourceId helper achieves by returning "".
There was a problem hiding this comment.
yeah, this is probably a good idea
| if activityId != "" { | ||
| return fmt.Sprintf("activity:%s", activityId) | ||
| } | ||
| return "" |
There was a problem hiding this comment.
What scenario do we hit this base case? Is it worth a log or something?
There was a problem hiding this comment.
We could, it's not expected.
| Identity: wtp.identity, | ||
| BinaryChecksum: wtp.workerBuildID, | ||
| Namespace: wtp.namespace, | ||
| ResourceId: fmt.Sprintf("workflow:%s", workflowId), |
There was a problem hiding this comment.
yeah, this is probably a good idea
| versionStamp *commonpb.WorkerVersionStamp, | ||
| deployment *deploymentpb.Deployment, | ||
| workerDeploymentOptions *deploymentpb.WorkerDeploymentOptions, | ||
| workflowId string, |
There was a problem hiding this comment.
can we just pass in resourceId instead here? All uses for these 2 vars are to immediately call getActivityResourceId
There was a problem hiding this comment.
We could, but I thought it made more sense to keep the more actually meaningful values until necessary. They seem more useful than resource id. If you disagree, I can change it.
| request := convertActivityResultToRespondRequest(wc.identity, opts.TaskToken, | ||
| data, opts.Err, dataConverter, failureConverter, wc.namespace, cancelAllowed, nil, nil, nil) | ||
| data, opts.Err, dataConverter, failureConverter, wc.namespace, cancelAllowed, nil, nil, nil, | ||
| opts.WorkflowID, "") |
There was a problem hiding this comment.
func (wc *WorkflowClient) CompleteActivity( above never passes in WorkflowID, so this is always false in that case
There was a problem hiding this comment.
So is the activityserializationcontext here broken as well?
| assert.Equal(t, tc.expectedResourceID, canceledRequest.ResourceId, tc.description) | ||
| } | ||
| } | ||
| }) |
There was a problem hiding this comment.
can we add a safety default case that fails the test?
| }{ | ||
| { | ||
| name: "StandardWorkflowTask", | ||
| workflowID: "test-workflow-completed-123", |
There was a problem hiding this comment.
Is there value in testing with an empty workflowID both here and in testWorkflowTaskFailedResourceID?
There was a problem hiding this comment.
We could validate it, though it is actually an unexpected case. Workflow task without a workflow id?
| func recordActivityHeartbeat(ctx context.Context, service workflowservice.WorkflowServiceClient, metricsHandler metrics.Handler, | ||
| identity string, taskToken []byte, details *commonpb.Payloads, | ||
| ) error { | ||
| namespace := getNamespaceFromActivityCtx(ctx) | ||
| request := &workflowservice.RecordActivityTaskHeartbeatRequest{ | ||
| TaskToken: taskToken, | ||
| Details: details, | ||
| Identity: identity, | ||
| Namespace: namespace, | ||
| TaskToken: taskToken, | ||
| Details: details, | ||
| Identity: identity, | ||
| Namespace: namespace, | ||
| ResourceId: getActivityResourceIdFromCtx(ctx), | ||
| } |
| func getActivityResourceIdFromCtx(ctx context.Context) string { | ||
| env := getActivityEnvironmentFromCtx(ctx) | ||
| if env == nil { | ||
| return "" |
There was a problem hiding this comment.
do we want test coverage for when env == nil?
| expectedResourceID: "workflow:test-workflow-completed-123", | ||
| }, | ||
| { | ||
| name: "DifferentWorkflowID", |
There was a problem hiding this comment.
What's the point of this second test case? from my reading it's testing the exact same as the first run?
| name: "WorkerHeartbeat", | ||
| groupingKey: "test-worker-grouping-key-123", | ||
| expectedResourceID: "worker:test-worker-grouping-key-123", | ||
| }, |
There was a problem hiding this comment.
single entry, no need for table style for this test
Summary
Implement logic to populate resource_id fields in request messages to support multi-cell request routing based on resource IDs.
This PR addresses the need to populate the newly added resource_id fields in various request messages so that the proxy can extract them and create appropriate temporal-resource-id headers for routing.
Changes
Workflow Task Requests
RespondWorkflowTaskCompletedRequest.resource_id→ populated with workflow ID from task contextRespondWorkflowTaskFailedRequest.resource_id→ populated with workflow ID from task contextActivity Task Requests
RespondActivityTaskCompletedRequest.resource_id→ workflow ID for workflow activities, activity ID for standaloneRespondActivityTaskCompletedByIdRequest.resource_id→ workflow ID for workflow activities, activity ID for standaloneRespondActivityTaskFailedRequest.resource_id→ workflow ID for workflow activities, activity ID for standaloneRespondActivityTaskFailedByIdRequest.resource_id→ workflow ID for workflow activities, activity ID for standaloneRespondActivityTaskCanceledRequest.resource_id→ workflow ID for workflow activities, activity ID for standaloneRespondActivityTaskCanceledByIdRequest.resource_id→ workflow ID for workflow activities, activity ID for standaloneActivity Heartbeat Requests
RecordActivityTaskHeartbeatRequest.resource_id→ extracted from activity contextRecordActivityTaskHeartbeatByIdRequest.resource_id→ workflow ID for workflow activities, activity ID for standaloneBatch Operation Requests
ExecuteMultiOperationRequest.resource_id→ workflow ID from the start operationWorker Requests
RecordWorkerHeartbeatRequest.resource_id→ worker grouping key from clientImplementation Details
Helper Functions Added
getActivityResourceId(workflowId, activityId): Determines appropriate resource ID (workflow ID if present, otherwise activity ID)getActivityResourceIdFromCtx(ctx): Extracts resource ID from activity context using activityEnvironmentLogic
Test plan
TestTemporalHeaderInterceptorpasses (validates header extraction works)Notes
RespondQueryTaskCompletedRequestdoes not have aresource_idfieldFetchWorkerConfigRequestandUpdateWorkerConfigRequestresource_id fields were not implemented as these APIs are not yet implemented in the SDKTestTemporalHeaderInterceptorvalidates that the temporal-resource-id header is correctly set to "workflow:test-workflow-id", confirming the entire pipeline works🤖 Generated with Claude Code