Context
This is a design question, not a bug report — I'd like your steer before building, because the answer decides whether a fairly large body of follow-up work is even feasible in gortex's model.
I'm using gortex to build a cross-repo call graph over a polyglot Temporal estate (Go workflows/activities + Java services). The Go→Go Temporal layer already works well (internal/resolver/temporal_calls.go, internal/parser/languages/golang_temporal.go), and I've sent some incremental Go-side additions in #78 (query/signal/update handler edges) and #79 (env-var-with-default dispatch names). The Java→Go bridge is discussed in #77.
But when I point gortex at real-world workflow code, almost none of the activity dispatch resolves — and after digging in, the cause isn't Temporal-specific. It comes down to two generic capabilities the engine deliberately doesn't have today. Before I (or anyone) writes Temporal-pattern-specific code, I think these two need a decision, because every higher-level pattern bottoms out on them.
The two gaps
1. Constant value is not retained
emitConst (internal/parser/languages/golang.go) creates a KindConstant node but stores only visibility in Meta — the literal value is dropped. So for the extremely common shape:
const ChargeCardActivity = "ChargeCard"
...
workflow.ExecuteActivity(ctx, ChargeCardActivity, …)
goTemporalNameFromExpr extracts the identifier ("ChargeCardActivity"), not the value ("ChargeCard"), so the registry/resolver can never match it to the activity. Real codebases name activities almost exclusively through constants.* references, so this single gap silently drops the majority of edges.
2. Dispatch through a user wrapper is invisible
Detection requires the call site to be literally workflow.ExecuteActivity(...) (the receiver != "workflow" gate in goTemporalDispatchKind). In practice teams wrap it:
func executeActivity[T any](ctx workflow.Context, ao workflow.ActivityOptions, name string, args ...any) (T, error) {
var out T
err := workflow.ExecuteActivity(ctx, name, args...).Get(ctx, &out)
return out, err
}
// caller:
executeActivity[Resp](ctx, ao, ChargeCardActivity, req) // ← invisible to gortex
There's no interprocedural argument-flow, so the name passed into the wrapper is never connected to the workflow.ExecuteActivity(ctx, name, …) inside it. (The same shape covers prepareActivity() wrappers and a shared workflowutils module.)
The only existing data-flow shortcut in the tree is the one I added in #79 (goTemporalEnvDefaultName), and it explicitly calls itself a "deliberately narrow data-flow shortcut, not general constant propagation" — so I don't want to grow it into something architectural without your blessing.
The questions
I know gortex is intentionally AST-local and precision-first, so I'm not assuming either of these is welcome. Concretely:
Q1 — Const value retention. Would you accept storing a KindConstant's literal value (when the RHS is a string/numeric literal) in Meta["value"], and a resolver step that resolves a same-package/imported const reference to that value for the temporal name argument? Or is dropping the value a deliberate choice (graph size, ambiguity with iota/computed consts) you'd rather keep?
Q2 — Bounded wrapper-following. Would you accept a bounded interprocedural step — one level deep — that, for a user function which itself calls workflow.ExecuteActivity(ctx, <param>, …), propagates a caller-supplied literal/const argument into the dispatch name? Strictly: single level, parameter must flow directly to the dispatch's name position, literal/const only, no general taint analysis.
If yes to either, I'd propose to:
If you'd rather not take gortex into any interprocedural/const-propagation territory, that's completely fair — I'd then document these as known blind spots and lean on a hints mechanism (e.g. // gortex:provides temporal-activity:ChargeCard or .gortex.yaml) instead. Either way, knowing your preference shapes everything downstream.
Why it matters for the rest
A larger plan of mine (resolving Temporal dispatch across ~50 repos through 7 dispatch patterns) depends almost entirely on these two: patterns built on constants and wrappers (the dominant ones) can't be resolved without Q1/Q2 regardless of how much Temporal-specific code is added. The genuinely cheap, in-grain pieces (e.g. SetSignalHandler, SignalExternalWorkflow/QueryWorkflow detection, Java @…Method(name=) parsing) I'm happy to send as standalone PRs independent of this decision — they don't need data-flow.
Thanks for the engine, and for any steer here.
Context
This is a design question, not a bug report — I'd like your steer before building, because the answer decides whether a fairly large body of follow-up work is even feasible in gortex's model.
I'm using gortex to build a cross-repo call graph over a polyglot Temporal estate (Go workflows/activities + Java services). The Go→Go Temporal layer already works well (
internal/resolver/temporal_calls.go,internal/parser/languages/golang_temporal.go), and I've sent some incremental Go-side additions in #78 (query/signal/update handler edges) and #79 (env-var-with-default dispatch names). The Java→Go bridge is discussed in #77.But when I point gortex at real-world workflow code, almost none of the activity dispatch resolves — and after digging in, the cause isn't Temporal-specific. It comes down to two generic capabilities the engine deliberately doesn't have today. Before I (or anyone) writes Temporal-pattern-specific code, I think these two need a decision, because every higher-level pattern bottoms out on them.
The two gaps
1. Constant value is not retained
emitConst(internal/parser/languages/golang.go) creates aKindConstantnode but stores onlyvisibilityinMeta— the literal value is dropped. So for the extremely common shape:goTemporalNameFromExprextracts the identifier ("ChargeCardActivity"), not the value ("ChargeCard"), so the registry/resolver can never match it to the activity. Real codebases name activities almost exclusively throughconstants.*references, so this single gap silently drops the majority of edges.2. Dispatch through a user wrapper is invisible
Detection requires the call site to be literally
workflow.ExecuteActivity(...)(thereceiver != "workflow"gate ingoTemporalDispatchKind). In practice teams wrap it:There's no interprocedural argument-flow, so the
namepassed into the wrapper is never connected to theworkflow.ExecuteActivity(ctx, name, …)inside it. (The same shape coversprepareActivity()wrappers and a sharedworkflowutilsmodule.)The only existing data-flow shortcut in the tree is the one I added in #79 (
goTemporalEnvDefaultName), and it explicitly calls itself a "deliberately narrow data-flow shortcut, not general constant propagation" — so I don't want to grow it into something architectural without your blessing.The questions
I know gortex is intentionally AST-local and precision-first, so I'm not assuming either of these is welcome. Concretely:
Q1 — Const value retention. Would you accept storing a
KindConstant's literal value (when the RHS is a string/numeric literal) inMeta["value"], and a resolver step that resolves a same-package/imported const reference to that value for the temporal name argument? Or is dropping the value a deliberate choice (graph size, ambiguity withiota/computed consts) you'd rather keep?Q2 — Bounded wrapper-following. Would you accept a bounded interprocedural step — one level deep — that, for a user function which itself calls
workflow.ExecuteActivity(ctx, <param>, …), propagates a caller-supplied literal/const argument into the dispatch name? Strictly: single level, parameter must flow directly to the dispatch's name position, literal/const only, no general taint analysis.If yes to either, I'd propose to:
OriginSpeculative/ low confidence /MetaSpeculative, like feat(temporal): resolve activity/workflow dispatch names from env-var-with-default vars #79) so they never inflate the high-confidence graph, and/or gate them behind a flag;EdgeCalls+via=temporal.*meta convention (no new edge/node kinds — blast-radius already traverses these generically viaEdgeCalls).If you'd rather not take gortex into any interprocedural/const-propagation territory, that's completely fair — I'd then document these as known blind spots and lean on a hints mechanism (e.g.
// gortex:provides temporal-activity:ChargeCardor.gortex.yaml) instead. Either way, knowing your preference shapes everything downstream.Why it matters for the rest
A larger plan of mine (resolving Temporal dispatch across ~50 repos through 7 dispatch patterns) depends almost entirely on these two: patterns built on constants and wrappers (the dominant ones) can't be resolved without Q1/Q2 regardless of how much Temporal-specific code is added. The genuinely cheap, in-grain pieces (e.g.
SetSignalHandler,SignalExternalWorkflow/QueryWorkflowdetection, Java@…Method(name=)parsing) I'm happy to send as standalone PRs independent of this decision — they don't need data-flow.Thanks for the engine, and for any steer here.