Unwrap PortableValue before invoking edge Condition and fan-out Assigner - #708
Conversation
There was a problem hiding this comment.
Pull request overview
This PR fixes edge routing behavior in EdgeRunner.PrepareDeliveryForEdge so caller-supplied edge delegates (Condition and fan-out Assigner) observe the concrete message value rather than a workflow.PortableValue wrapper when the message’s runtime type can be resolved. This aligns edge-delegate behavior with the existing router unwrapping logic and improves cross-SDK parity.
Changes:
- Add
EdgeRunner.unwrapMessageto resolveworkflow.PortableValueto its declared runtime type (when possible). - Invoke
edge.Conditionandedge.Assignerwith the unwrapped message value (and thread it through target selection). - Add black-box tests verifying unwrapping for both
ConditionandAssigner.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| workflow/internal/execution/edgerunner.go | Unwraps PortableValue before invoking edge Condition/Assigner, and updates target selection to use the unwrapped message. |
| workflow/internal/execution/edgerunner_test.go | Adds tests asserting Condition/Assigner receive the concrete message when PortableValue runtime type is resolvable. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This comment has been minimized.
This comment has been minimized.
806a12f to
2294e30
Compare
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
PrepareDeliveryForEdge passed the raw envelope.Message to the caller-supplied Condition (func(any) bool) and fan-out Assigner (func(int, any)). When the message is a workflow.PortableValue, a user delegate that type-asserts the concrete type (e.g. m.(Order).Total > 100) sees the wrapper and fails: the Condition drops or misroutes the message, and an Assigner assertion can panic the superstep since PrepareDeliveryForEdge has no recover(). Resolve the PortableValue to its declared runtime type once (via messageRuntimeType) and use the unwrapped value for both delegates, falling back to the raw message when the type cannot be resolved. This mirrors messageRouter.routeMessage and the .NET runtime, which unwrap PortableValue before invoking the delegate.
dab3656 to
a54d3c0
Compare
Parity Review: No IssuesThis PR fixes a bug in Cross-repo parity: The change explicitly aligns Go behavior with the .NET runtime, which already unwraps Label assessment: No Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
|
Requesting changes for .NET semantic equivalence The current implementation fixes the reported routing issue, but it does not reproduce .NET semantics. .NET determines whether to unwrap from the delegate's declared generic type:
This PR instead unwraps inside Exact parity cannot be implemented solely in
The adapters should:
Raw Please add parity tests covering:
Without retaining the delegate's declared type through a typed adapter or equivalent metadata, this PR cannot provide .NET semantic equivalence. |
What
EdgeRunner.PrepareDeliveryForEdgepassed the rawenvelope.Messageto the caller-supplied edgeCondition(func(any) bool) and fan-outAssigner(func(int, any)). When the in-flight message is aworkflow.PortableValue(e.g. a message delayed-deserialized after a checkpoint restore), a user delegate that type-asserts the concrete type sees the wrapper instead:m.(Order).Total > 100fails its assertion, so the edge silently drops or misroutes the message;Assignerthat type-asserts panics the superstep, sincePrepareDeliveryForEdgehas norecover().This resolves the
PortableValueto its declared runtime type once (via the existingmessageRuntimeType) and threads the unwrapped value into both theConditioncall andselectedTargetIDsfor theAssigner. It falls back to the raw message whenever the runtime type cannot be resolved.Why
This mirrors
messageRouter.routeMessage, which already unwrapspvalue.As(info.runtimeType)before invoking a handler, and matches the .NET runtime, which unwrapsmaybeObj is PortableValuebefore invoking the edge delegate. Cross-SDK parity: the concrete message the user's edge delegates observe should not depend on whether the message happens to be carried as aPortableValue.Tests
Added two black-box tests in the canonical
edgerunner_test.gothat build aMessageEnvelopewhoseMessageis aPortableValuewith a resolvable runtime type:TestPrepareDeliveryForEdge_UnwrapsPortableValueForCondition— a direct edge whoseConditiontype-asserts the concrete type; asserts the delegate sees the concrete type and delivery happens.TestPrepareDeliveryForEdge_UnwrapsPortableValueForAssigner— a fan-out edge whoseAssignertype-asserts the concrete type; asserts routing to the selected sink.Both fail before the change (Condition/Assigner see a
PortableValue) and pass after.go build ./...,go vet, andgo test ./workflow/...are green.