Commit 5ad31e6
feat(workflow): align history propagation API with go-sdk (#1825)
* feat(workflow): align history propagation API with go-sdk
Cassie (durabletask-go author) flagged the .NET surface for cross-SDK
divergence post-merge of dotnet-sdk#1802 / #1818. This rewrites the
public history-propagation API to match the go-sdk shape — same one the
python-sdk just adopted (python-sdk#1047). Issue dotnet-sdk#1801 was
closed before her review; this PR delivers what the issue originally
described.
Three concrete gaps closed:
1. Activity-level opt-in (was missing entirely)
- PropagationScope moved from ChildWorkflowTaskOptions to base
WorkflowTaskOptions; ChildWorkflowTaskOptions inherits it.
- WithHistoryPropagation() extension method added on the base record.
- scheduleTaskAction.HistoryPropagationScope is now wired in
WorkflowOrchestrationContext.CallActivityInternalAsync so activities
can opt into propagation, matching CallChildWorkflowInternalAsync.
- Without this, the Go SDK's reference example (SettlePayment activity
using PropagateOwnHistory) literally cannot be ported to .NET.
2. Read API rewritten as high-level resolvers (was lossy FilterBy* + a
PropagatedHistoryEvent record that dropped input/output/failure
payloads)
- PropagatedHistory.FilterByAppId/InstanceId/WorkflowName removed.
- PropagatedHistory now exposes GetWorkflows(), GetWorkflowsByName(),
GetLastWorkflowByName(), GetAppIds(), GetWorkflowsByAppId(),
GetWorkflowsByInstanceId().
- New WorkflowResult class with InstanceId/AppId/Name plus
GetActivitiesByName(), GetLastActivityByName(),
GetChildWorkflowsByName(), GetLastChildWorkflowByName() — mirrors
durabletask-go's GetLastWorkflowByName / GetLastActivityByName /
GetLastChildWorkflowByName renames from durabletask-go#105.
- New ActivityResult record carries Name, Started, Completed, Failed,
Input, Output, FailureDetails — matching the Go/Python equivalents
so chain-of-custody patterns line up.
- New ChildWorkflowResult record with the equivalent shape.
3. Event payload preserved internally (was discarded by ConvertChunk)
- ConvertChunk in WorkflowOrchestrationContext now parses raw events,
walks them to resolve TaskScheduled <-> TaskCompleted/Failed and
ChildWorkflowInstanceCreated <-> ChildWorkflowInstanceCompleted/
Failed by scheduleId, and produces fully-populated ActivityResult /
ChildWorkflowResult instances. SDK retries reuse TaskExecutionId so
matching is on scheduleId (matching Go/Python semantics).
- Public API does not leak the proto HistoryEvent type — resolution
happens at construction time inside Dapr.Workflow.
Additional surface additions:
- PropagationNotFoundException for missing-name lookups (mirrors
Python's PropagationNotFoundError / Go's error returns).
- Static WorkflowHistory.PropagateLineage() / PropagateOwnHistory()
factory helpers for go-sdk call-site parity.
Removed (clean break — 1.18 unreleased): PropagatedHistoryEntry,
PropagatedHistoryEvent, HistoryEventKind, FilterByAppId,
FilterByInstanceId, FilterByWorkflowName.
Tests:
- WorkflowHistoryPropagationTests.cs rewritten end-to-end to cover the
new resolvers, query helpers, factory helpers, activity-level scope
wiring, and child-workflow-level scope wiring.
- HistoryPropagationWorkflowTests.cs (integration) updated to use
GetWorkflows().Count in place of Entries.Count.
Refs: #1801, dapr/durabletask-go#105, dapr/go-sdk#823,
dapr/python-sdk#1047
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* fix(workflow): address code-review feedback on history-propagation alignment
- Document the `new`-hiding contract on ChildWorkflowTaskOptions
.WithHistoryPropagation and add a regression test that asserts the
returned type is ChildWorkflowTaskOptions (not the base record), so
InstanceId survives the with-expression.
- Add the standard `()`, `(string)`, and `(string, Exception)` constructors
on PropagationNotFoundException so callers can wrap inner exceptions.
- Alias StringValue alongside the existing Timestamp alias in
WorkflowOrchestrationContext so the propagation helper signature stays
consistent with the rest of the file.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* test(workflow): clarify chunk-order test variable names
Renames the test fixtures in GetPropagatedHistory_PreservesChunkOrder so the
variable order matches the documented oldest-first chunk ordering (index 0 is
the oldest ancestor, the last chunk is the immediate parent). No behavior change.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* fix(workflow): pass StringValue-wrapped fields directly as strings
protoc unwraps google.protobuf.StringValue to a plain string in the
generated C# (only the wire codec uses the wrapper). The
StringValueOrNull(StringValue?) helper added in this branch expected
the wrapper type, breaking the build with CS1503 at the three call
sites in ResolveActivity / ResolveChildWorkflow. Drop the helper and
pass the generated string fields straight through — they are already
nullable at runtime and ActivityResult/ChildWorkflowResult accept
string? for Input/Output.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* test(workflow): assign string directly to wrapper-typed fields
Same StringValue mismatch as the production fix — protoc-generated
properties for google.protobuf.StringValue fields are plain string,
not the wrapper. Drop the new StringValue { Value = ... } wrappers
in the test helpers.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* refactor(workflow): rename propagation types and adopt TryGet pattern
Addresses Whit's review on #1825:
- Rename ActivityResult -> PropagatedHistoryActivityResult
- Rename ChildWorkflowResult -> PropagatedHistoryChildWorkflowResult
- Rename WorkflowResult -> PropagatedHistoryEntry (primary constructor)
- Drop WorkflowHistory static class; callers pass HistoryPropagationScope directly
- Switch GetLast*ByName to TryGet*ByName + drop PropagationNotFoundException
- Drop chunk/chain terminology from public XML docs
- Surface malformed propagated event bytes via InvalidProtocolBufferException
instead of silently skipping
- Update unit tests to new names and TryGet asserts
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* test(workflow): rename propagation test cases to match renamed types
Test names previously embedded the old type names (ActivityResult,
ChildWorkflowResult); rename to the more general Activity_/ChildWorkflow_
prefix to avoid confusion with the renamed public types.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* fix(workflow): match propagated-history names and app IDs case-insensitively
Workflow / activity names register through WorkflowsFactory with
StringComparer.OrdinalIgnoreCase, and app IDs are matched case-insensitively
on the invocation path. Align the propagated history lookups (GetAppIds,
GetWorkflowsByName, TryGetLastWorkflowByName, GetWorkflowsByAppId,
GetActivitiesByName, TryGetLastActivityByName, GetChildWorkflowsByName,
TryGetLastChildWorkflowByName) with that contract so callers don't get
surprising misses or duplicate logical IDs that only differ by casing.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* perf(workflow): pre-index completion events in ConvertChunk
ConvertChunk previously rescanned the full event list inside ResolveActivity
and ResolveChildWorkflow, making conversion O(n²) in the number of history
events. Pre-index TaskCompleted / TaskFailed by TaskScheduledId (and the
ChildWorkflowInstance counterparts) up front so each scheduled item resolves
in O(1).
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* refactor(workflow): rename PropagatedHistory backing field to _entries
The private field and ctor parameter on PropagatedHistory are now named
after the value type they hold (PropagatedHistoryEntry) rather than the
'workflows' role those entries play today. Public API surface is
unchanged.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* refactor(workflow): use generic propagated-history method names; add Status enum
Addresses Whit's 2026-05-24 review.
Rename the PropagatedHistory query family to scope-neutral names so the public
surface need not change if propagated history ever carries non-workflow entries:
GetWorkflows() -> GetEntries()
GetWorkflowsByName() -> FilterByWorkflowName()
GetWorkflowsByAppId() -> FilterByAppId()
GetWorkflowsByInstanceId() -> FilterByInstanceId()
Add PropagatedHistoryTaskStatus (Pending/Completed/Failed) and a computed
Status property on PropagatedHistoryActivityResult and
PropagatedHistoryChildWorkflowResult so callers can switch on a single value.
The Started/Completed/Failed flags are retained for go-sdk/python-sdk parity;
Status is a projection of them, with Failed taking precedence over Completed.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
* test(workflow): fix case-insensitive AppId filter expectation
FilterByAppId matches case-insensitively, so two entries whose app IDs
differ only in casing ("AppA" / "appa") both match a query for "APPA".
The de-duped GetAppIds list collapses to one, but the filter returns both;
assert two matches instead of one.
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
---------
Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
Co-authored-by: Whit Waldo <whit.waldo@innovian.net>1 parent 29eb639 commit 5ad31e6
12 files changed
Lines changed: 869 additions & 358 deletions
File tree
- src
- Dapr.Workflow.Abstractions
- Dapr.Workflow/Worker/Internal
- test
- Dapr.IntegrationTest.Workflow
- Dapr.Workflow.Test/Worker/Internal
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
| 18 | + | |
18 | 19 | | |
19 | 20 | | |
20 | 21 | | |
21 | | - | |
22 | | - | |
| 22 | + | |
23 | 23 | | |
24 | 24 | | |
25 | | - | |
26 | | - | |
27 | | - | |
28 | | - | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
29 | 33 | | |
30 | 34 | | |
31 | 35 | | |
32 | 36 | | |
33 | 37 | | |
34 | 38 | | |
35 | | - | |
| 39 | + | |
36 | 40 | | |
37 | | - | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
38 | 44 | | |
39 | 45 | | |
40 | 46 | | |
41 | 47 | | |
42 | 48 | | |
43 | 49 | | |
44 | | - | |
45 | | - | |
46 | | - | |
| 50 | + | |
| 51 | + | |
47 | 52 | | |
48 | | - | |
| 53 | + | |
49 | 54 | | |
50 | 55 | | |
51 | | - | |
| 56 | + | |
52 | 57 | | |
53 | | - | |
54 | | - | |
55 | | - | |
| 58 | + | |
56 | 59 | | |
57 | | - | |
58 | | - | |
59 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
60 | 71 | | |
61 | 72 | | |
62 | 73 | | |
63 | | - | |
| 74 | + | |
| 75 | + | |
64 | 76 | | |
65 | | - | |
66 | | - | |
67 | | - | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
68 | 80 | | |
69 | | - | |
70 | | - | |
71 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
72 | 120 | | |
73 | 121 | | |
74 | 122 | | |
75 | | - | |
| 123 | + | |
| 124 | + | |
76 | 125 | | |
77 | | - | |
78 | | - | |
79 | | - | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
80 | 129 | | |
81 | | - | |
82 | | - | |
83 | | - | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
84 | 134 | | |
85 | 135 | | |
Lines changed: 50 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
Lines changed: 48 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
0 commit comments