Skip to content

[SDK Ergonomics] NEXUS-519: Support Query-backed Nexus Operations - #11274

Open
mavemuri wants to merge 2 commits into
mainfrom
mavemuri/nexus-query
Open

[SDK Ergonomics] NEXUS-519: Support Query-backed Nexus Operations#11274
mavemuri wants to merge 2 commits into
mainfrom
mavemuri/nexus-query

Conversation

@mavemuri

@mavemuri mavemuri commented Jul 24, 2026

Copy link
Copy Markdown

What changed?

Adds server support for WorkflowQuery-backed Nexus Operations

Why?

Part of effort to expose all Temporal primitives as Nexus Operations

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)
  • added new functional test(s)

TODO:

@mavemuri mavemuri added the request-claude-review Request Claude review for this PR label Jul 24, 2026
Comment thread go.mod Outdated
Comment thread service/history/api/queryworkflow/api.go
Comment thread common/nexus/links.go
Comment thread tests/query_workflow_test.go Outdated
@mavemuri
mavemuri force-pushed the mavemuri/nexus-query branch 3 times, most recently from c6bd436 to d0784fb Compare July 25, 2026 00:11
Comment thread go.mod
go.opentelemetry.io/otel/sdk/metric v1.43.0
go.opentelemetry.io/otel/trace v1.44.0
go.temporal.io/api v1.63.4
go.temporal.io/api v1.62.15-0.20260622232249-060670b1b866

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placeholder until api change is merged

@mavemuri
mavemuri force-pushed the mavemuri/nexus-query branch 4 times, most recently from 52ae6e5 to 2657210 Compare July 25, 2026 01:07
@mavemuri
mavemuri marked this pull request as ready for review July 27, 2026 16:02
@mavemuri
mavemuri requested review from a team as code owners July 27, 2026 16:02
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @mavemuri's task in 7m 1s —— View job


Review complete

  • Read review skill + .github/copilot-instructions.md
  • Analyzed the diff (common/nexus/links.go, service/history/api/queryworkflow/api.go, tests/query_workflow_test.go, go.mod/go.sum, matching test import)
  • Traced the link through the frontend passthrough (workflow_handler.go:3282), common/links.Validate, and the ConvertNexusLinksToProtoLinks callers
  • Posted 5 inline comments

Overall the change looks sound: the resp.GetResponse() == nil guard makes the deferred block safe on every return path, the defer is registered after the early error returns so only real responses get a link, and the functional test covers both the "Query processed" (direct-through-matching) and "Query rejected" (early-return) branches. Findings are inline — the one worth a real decision is that common/links.Validate rejects the Link_Workflow variant, so the link this PR returns can't be attached to a follow-up request.
Branch: mavemuri/nexus-query

Comment thread service/history/api/queryworkflow/api.go Outdated
// Note: QueryWorkflow should not alter mutable state, so it is safe to ignore error and not clear ms.
workflowLease.GetReleaseFn()(nil)
}()
defer func() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

common/links.Validate has no *commonpb.Link_Workflow_ case — it falls into default: and returns InvalidArgument: "unsupported link variant" (common/links/validator.go:60-61).

That makes the link this PR returns a dead end for the main thing links are for: taking a link you got back and attaching it to a follow-up request. A client that does

qResp, _ := c.WorkflowService().QueryWorkflow(ctx, ...)
c.WorkflowService().StartWorkflowExecution(ctx, &workflowservice.StartWorkflowExecutionRequest{
    ..., Links: []*commonpb.Link{qResp.GetLink()},
})

gets InvalidArgument: unsupported link variant. Same for SignalWorkflowExecution, SignalWithStart, and UpdateWorkflowExecution (service/frontend/workflow_handler.go:693, 2241, 2286, 2350, 2445) plus the CHASM validators (chasm/lib/workflow/validator.go:220, chasm/lib/activity/link_validator.go:34).

Strictly this gap predates the PR — updateworkflow already emits Link_Workflow for rejected updates — but this change makes the variant reachable from every query response, so it's worth closing here. Adding the case alongside the Link_Activity_ one is a few lines:

case *commonpb.Link_Workflow_:
	if t.Workflow.GetNamespace() == "" {
		return serviceerror.NewInvalidArgument("workflow link must not have an empty namespace field")
	}
	if t.Workflow.GetWorkflowId() == "" {
		return serviceerror.NewInvalidArgument("workflow link must not have an empty workflow ID field")
	}
	if t.Workflow.GetRunId() == "" {
		return serviceerror.NewInvalidArgument("workflow link must not have an empty run ID field")
	}

Comment thread common/nexus/links.go
Comment thread go.mod
go.opentelemetry.io/otel/sdk/metric v1.43.0
go.opentelemetry.io/otel/trace v1.44.0
go.temporal.io/api v1.63.4
go.temporal.io/api v1.62.15-0.20260622232249-060670b1b866

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond the replace placeholder you already flagged below: this line also downgrades the require from v1.63.4 (which main just moved to in #11264) to the older v1.62.15-0.20260622232249-060670b1b866 pseudo-version. The replace masks that today, so it's easy to miss — but if the replace is dropped without also restoring the require, the module silently regresses to a pre-v1.63.4 API.

When temporalio/api#842 lands, the cleanup is: delete the replace, set this back to the released tag (>= v1.63.4), and restore the two go.temporal.io/api lines that were removed from go.sum.

Comment thread service/matching/task_queue_partition_manager_test.go
@mavemuri
mavemuri force-pushed the mavemuri/nexus-query branch from 2657210 to ad84e98 Compare July 27, 2026 19:02
@mavemuri mavemuri added request-claude-review Request Claude review for this PR and removed request-claude-review Request Claude review for this PR labels Jul 27, 2026
Comment thread go.mod
Comment thread common/links/validator.go
Comment thread service/history/api/queryworkflow/api.go Outdated
Comment thread common/nexus/links_test.go Outdated
@Quinn-With-Two-Ns

Copy link
Copy Markdown
Contributor

High level looks good, but I would address all the AI's comments except for #11274 (comment) of course

@mavemuri
mavemuri force-pushed the mavemuri/nexus-query branch from ad84e98 to 90722a6 Compare July 28, 2026 23:59
@mavemuri mavemuri added request-claude-review Request Claude review for this PR and removed request-claude-review Request Claude review for this PR labels Jul 29, 2026
Comment thread go.mod
Comment thread common/nexus/links.go
Variant: &commonpb.Link_Activity_{Activity: link},
})
case string((&commonpb.Link_Workflow{}).ProtoReflect().Descriptor().FullName()):
link, err := temporalnexus.ConvertNexusLinkToLinkWorkflow(nexusLink)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two sibling cases use this repo's own converters (ConvertNexusLinkToLinkWorkflowEvent, ConvertNexusLinkToLinkActivity in common/nexus/link_converter.go), while this one reaches into go.temporal.io/api/temporalnexus. That's the only use of go.temporal.io/api/temporalnexus anywhere in the repo, and it is the sole reason for the api-go fork pin in go.mod.

common/nexus/link_converter.go opens with an explicit policy for exactly this situation:

This file is duplicated in sdk-go/temporalnexus/link_converter.go. Any changes here or there must be replicated. This is temporary until the temporal repo updates to the most recent SDK version.

Adding a local ConvertNexusLinkToLinkWorkflow next to the existing converters would keep all three URL path formats defined in one place, keep this switch internally consistent, and drop the need for the replace directive entirely. It's a small function and the file already owns the urlPath*Template/regex conventions it would follow.

@mavemuri mavemuri Jul 29, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rejected
real fix is to eventually point all the converters to the helper from api-go for consistency
This pr intentionally addresses one to keep it scoped while avoid additional duplication. Cleaning up all the duplicates will be a different, more scoped pr

Comment thread common/nexus/links_test.go Outdated
Comment thread tests/query_workflow_test.go Outdated
@mavemuri
mavemuri force-pushed the mavemuri/nexus-query branch from 90722a6 to a672bd2 Compare July 29, 2026 00:41
@mavemuri mavemuri added request-claude-review Request Claude review for this PR and removed request-claude-review Request Claude review for this PR labels Jul 29, 2026
Comment thread go.mod
go.opentelemetry.io/otel/sdk/metric v1.43.0
go.opentelemetry.io/otel/trace v1.44.0
go.temporal.io/api v1.63.4
go.temporal.io/api v1.62.15-0.20260622232249-060670b1b866

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This pins go.temporal.io/api to an unreleased fork via the replace at the bottom of the file, and at the same time downgrades the require line from v1.63.4 to v1.62.15-0.20260622232249-060670b1b866.

replace directives are only honored in the main module — they are ignored for anyone consuming go.temporal.io/server as a dependency. So downstream consumers will resolve the older v1.62.15-… api, which has neither QueryWorkflowResponse.Link nor temporalnexus.ConvertNexusLinkToLinkWorkflow, and common/nexus/links.go will fail to compile for them. Even with the replace kept in place locally, the require line shouldn't go backwards.

Flagging as blocking merge, consistent with the PR's own TODO on temporalio/api#842: once that's released, drop the replace and set require go.temporal.io/api to the released tag (≥ v1.63.4).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignore
this is a placeholder until the relevant api/api-go changes are reviewed and merged(already noted in TODO in this pr conversation)
it is kept this way for now for convenience of reviewing and running CI/tests etc

if retError != nil || resp.GetResponse() == nil {
return
}
// Add link to Workflow regardless of query status as long as there isn't an error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "as long as there isn't an error" clause just restates the guard three lines above. What isn't obvious from the code is why this is a Link_Workflow rather than a Link_WorkflowEvent — same rationale as the rejected-update case in updateworkflow/api.go.

Suggested change
// Add link to Workflow regardless of query status as long as there isn't an error.
// Queries never write a history event, so link to the workflow itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

request-claude-review Request Claude review for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants