Skip to content

feat(temporal): detect outbound signal-send / query-call against running workflows#81

Merged
zzet merged 1 commit into
zzet:mainfrom
avfirsov:feat/temporal-signal-query-outbound
Jun 12, 2026
Merged

feat(temporal): detect outbound signal-send / query-call against running workflows#81
zzet merged 1 commit into
zzet:mainfrom
avfirsov:feat/temporal-signal-query-outbound

Conversation

@avfirsov

Copy link
Copy Markdown
Contributor

Summary

Detect the consumer side of the Temporal signal/query namespaces — code that signals or queries an already-running workflow by name — and surface it as graph edges. This complements the in-workflow handler edges (via=temporal.handler, added in #78) with the matching sender/caller side.

Changes

Recognises three call shapes and tags the emitted EdgeCalls edge:

Call via kind
workflow.SignalExternalWorkflow(ctx, wid, rid, "name", arg) (workflow → workflow) temporal.signal-send signal
client.SignalWorkflow(ctx, wid, rid, "name", arg) (service → workflow) temporal.signal-send signal
client.QueryWorkflow(ctx, wid, rid, "name", args...) (service → workflow) temporal.query-call query

The signal/query name is the 4th positional argument and must be a string literal (names are matched by string at runtime, so a variable/constant is left undetected — high-precision, consistent with the existing detectors). SignalExternalWorkflow is gated on the canonical workflow receiver; SignalWorkflow/QueryWorkflow are client methods called on an arbitrary client variable, so they're matched by method name (the same precedent as the Register* helpers), kept precise by the string-literal name gate.

No new edge/node kinds — stays on the established EdgeCalls + via=temporal.* meta convention, so blast-radius/impact already traverse these generically.

A note on the Go SDK surface (API corrections)

While implementing this I verified the API against go.temporal.io/sdk and corrected a few things that don't exist in the Go SDK:

  • No workflow.SetSignalHandler — signals are received via GetSignalChannel (already detected). The genuinely-missing handler variants (SetQueryHandlerWithOptions, GetSignalChannelWithOptions) are added in feat(temporal): detect Go in-workflow query/signal/update handler declarations #78, not here.
  • No workflow.QueryWorkflow — querying is a client-side operation (Client.QueryWorkflow), handled accordingly above.
  • No SignalExternalWorkflowAsyncSignalExternalWorkflow already returns a Future.

Testing

  • All tests pass (go test ./...) for the touched package (internal/parser/languages); golangci-lint v2.11.4 run --timeout=10m0 issues.
  • New tests added — SignalExternalWorkflow, client.SignalWorkflow, client.QueryWorkflow, plus negatives for a non-literal name and an aliased-import SignalExternalWorkflow.
  • Benchmarks — n/a; per-call AST check on the existing walk.

Checklist

  • Code follows existing patterns (mirrors applyGoTemporalRegisterMeta; matches the Register* receiver-agnostic precedent with a literal-arg precision gate)
  • No unnecessary abstractions added
  • Language extractor includes Meta["methods"] for interfaces (if applicable) — n/a
  • Methods have EdgeMemberOf edges to their containing type (if applicable) — n/a

Relation to other PRs

Part of a small in-grain Temporal series, all independent and reviewable separately: #78 (in-workflow query/signal/update handler edges), #79 (env-var-with-default dispatch names), and this one. The larger cross-language Java→Go bridge and a couple of foundational engine questions are discussed in #77 and #80.

…ing workflows

Surface the consumer side of the Temporal signal/query namespaces:

    workflow.SignalExternalWorkflow(ctx, wid, rid, "name", arg)   // workflow -> workflow
    client.SignalWorkflow(ctx, wid, rid, "name", arg)            // service  -> workflow
    client.QueryWorkflow(ctx, wid, rid, "name", args...)         // service  -> workflow

Each emits an EdgeCalls edge tagged via=temporal.signal-send /
temporal.query-call carrying temporal_kind + temporal_name (the signal /
query name = the 4th positional argument, a string literal). These pair
with the in-workflow handler edges (via=temporal.handler) as the
sender/handler two sides of the signal/query contract.

SignalExternalWorkflow is gated on the canonical "workflow" receiver;
SignalWorkflow / QueryWorkflow live on the client and are matched by
method name (like the Register* helpers), kept high-precision by the
string-literal name gate. No new edge kinds — consistent with the
existing EdgeCalls + via=temporal.* convention.

Note on the Go SDK surface: there is no workflow.SetSignalHandler
(signals are received via GetSignalChannel), no workflow.QueryWorkflow
(querying is client-side), and no SignalExternalWorkflowAsync
(SignalExternalWorkflow already returns a Future).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
zzet added a commit that referenced this pull request Jun 12, 2026
Temporal cluster: merge #78/#79/#81 + cross-language Java→Go join (#77) + const retention (#80)
@zzet zzet merged commit 586af38 into zzet:main Jun 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants