Skip to content

fix(temporal): resolve boundary type names as strings (no leading colon for namespace-less)#352

Merged
wolfy-j merged 5 commits into
mainfrom
fix/temporal-workflow-type-name
Jun 17, 2026
Merged

fix(temporal): resolve boundary type names as strings (no leading colon for namespace-less)#352
wolfy-j merged 5 commits into
mainfrom
fix/temporal-workflow-type-name

Conversation

@skhaz

@skhaz skhaz commented Jun 16, 2026

Copy link
Copy Markdown
Member

Why

Spawning a workflow whose registry ID has no namespace (e.g. an external, PHP-hosted workflow) recorded the Temporal WorkflowType as ":PopulateItemRelation" instead of "PopulateItemRelation". Temporal type names are plain strings that only sometimes coincide with a registry ID; the runtime derived them by stringifying registry.ID ad-hoc across the dispatch paths, so a namespace-less ID rendered in its canonical :name form leaked into the type name. There was also no first-class way to name an external workflow.

What

  • Boundary names are plain strings. A single resolver (service/temporal/options/name.go) owns the rule: prefer an explicit name, else the qualified id (name when the namespace is empty, ns:name otherwise — never a leading colon). The qualify logic lives entirely in the temporal options package; registry.ID is untouched.
  • Explicit name controls. New workflow.name / activity.name spawn options (with temporal.* legacy aliases) and a MetaActivityName registration override mirroring the existing MetaWorkflowName.
  • All dispatch + registration sites (host spawn, child spawn/exec, workflow exec, activity dispatch, continue-as-new, listeners) resolve through the resolver. registry.ID stays the carrier on Start.Source (the local process path needs it as a key) but is demoted to a naming fallback.
  • Application-level tests under tests/app exercise the real Lua surface: a workflow registered under an explicit string name, spawned by a bare custom name and via the workflow.name option override.
  • Incidental fix: narrowed Message? -> Message at 13 call sites in 5 pre-existing temporal app fixtures (latent Luau type error that blocked the temporal app suite from loading; unrelated to naming).

Testing

  • make build-wippy + go build ./... green; golangci-lint 0 issues; go test -race ./service/temporal/... ./api/registry/ green.
  • Go unit/integration: resolver precedence/fallback (canonical vs legacy alias, empty/non-string/nil -> fallback, namespace-less -> no colon); host dispatch wire-capture (workflow.name override and namespace-less default -> PopulateItemRelation); workflow + activity listener register/unregister symmetry with a custom name. gremlins on the resolver: 100% killed.
  • Application surface against a real Temporal dev server: app suite 423/423, temporal suite 41/41 including the new workflow_custom_name test (bare-name dispatch + option override end-to-end).

@skhaz skhaz requested a review from wolfy-j June 16, 2026 21:04
@skhaz skhaz changed the title fix(temporal): use colon-free type name for namespace-less workflows fix(temporal): resolve boundary type names as strings (no leading colon for namespace-less) Jun 16, 2026
skhaz added 5 commits June 16, 2026 19:21
Why:
Spawning a workflow whose registry ID has no namespace (e.g. an external,
PHP-hosted workflow) recorded the Temporal WorkflowType as
":PopulateItemRelation" instead of "PopulateItemRelation". registry.ID.String()
renders a namespace-less ID in its canonical ":name" form, and the Temporal
layer used that string directly as the external workflow/activity type name.

What:
Add registry.ID.Qualified(), which returns "name" when the namespace is empty
and "ns:name" otherwise (never a leading colon), mirroring MarshalJSON. Switch
every Temporal type-name derivation -- both dispatch (start) and registration --
from String() to Qualified(), so namespaced names are unchanged while
namespace-less names lose the spurious colon symmetrically on both sides.
…name option

Why:
Temporal workflow/activity type names are plain strings that only sometimes
coincide with a registry ID. Deriving them by stringifying registry.ID ad-hoc
across the dispatch paths coupled the boundary to ID semantics (the source of
the leading-colon bug) and offered no way to name an external workflow whose
type string is not a registry entry.

What:
Add a single boundary-name resolver (service/temporal/options/name.go):
WorkflowName/ActivityName prefer an explicit "workflow.name"/"activity.name"
option (with temporal.* legacy alias) and fall back to id.Qualified();
WorkflowNameFromID/ActivityNameFromID cover sites with no options bag. Route
every dispatch site (host spawn, child spawn/exec, workflow exec, activity
dispatch, continue-as-new) through the resolver, demoting registry.ID to a
naming fallback while keeping it as the local-path key on Start.Source. Add the
symmetric MetaActivityName registration override mirroring MetaWorkflowName, so
an activity definition can declare a string name; default stays Qualified().
Why:
The colon-free type-name derivation was added as a registry.ID.Qualified()
method, putting Temporal-boundary concerns on the generic registry value type.

What:
Remove registry.ID.Qualified() (revert api/registry/id.go and id_test.go to
their original form) and move the logic into an unexported qualify() in
service/temporal/options/name.go, used by the WorkflowName/ActivityName
resolvers. The workflow and activity listeners now derive their fallback name
via temporaloptions.WorkflowNameFromID/ActivityNameFromID. Behaviour is
identical (bare name when the namespace is empty, "ns:name" otherwise).
Why:
Five temporal app-test fixtures passed a Message? value where payload_data
expects a non-optional Message, a latent Luau type error. It never surfaced
because CI always sets SKIP_TEMPORAL_TESTS, but it blocks the whole temporal
app suite from type-checking once the suite is enabled.

What:
Narrow the value at the 13 call sites with `msg as Message`, the cast idiom
already used elsewhere in these same files. Type-check only; no behaviour
change.
Why:
The workflow type-name behaviour had only Go-level coverage; the real Lua
application surface (process:spawn through a Temporal worker) was untested.

What:
Add a named_hello_workflow registered under an explicit string name
(meta.temporal.workflow.name) and a workflow_custom_name test that, via
spawn_monitored against the live worker, proves (a) a bare custom name
dispatches without a leading colon and (b) the workflow.name option overrides
the source id. Verified green against a real Temporal dev server
(temporal suite 41/41, app suite 423/423).
@skhaz skhaz force-pushed the fix/temporal-workflow-type-name branch from 3547d9e to a4d5a1d Compare June 16, 2026 22:22
@wolfy-j

wolfy-j commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

looks good

Comment thread api/registry/id.go Outdated
@skhaz skhaz requested a review from wolfy-j June 16, 2026 22:51
wolfy-j added a commit that referenced this pull request Jun 17, 2026
@wolfy-j wolfy-j merged commit 87d7114 into main Jun 17, 2026
4 checks passed
@wolfy-j wolfy-j deleted the fix/temporal-workflow-type-name branch June 17, 2026 01:03
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