feat(workflow): align history propagation API with go-sdk#1825
Open
nelson-parente wants to merge 3 commits into
Open
feat(workflow): align history propagation API with go-sdk#1825nelson-parente wants to merge 3 commits into
nelson-parente wants to merge 3 commits into
Conversation
Cassie (durabletask-go author) flagged the .NET surface for cross-SDK divergence post-merge of dotnet-sdk#1802 / dapr#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: dapr#1801, dapr/durabletask-go#105, dapr/go-sdk#823, dapr/python-sdk#1047 Signed-off-by: Nelson Parente <nelson_parente@live.com.pt>
…ignment - 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>
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Aligns the workflow history propagation surface added in #1802 / #1818 with the canonical go-sdk shape — the same one python-sdk just adopted in python-sdk#1047. @cicoyle (durabletask-go author) flagged the divergence post-merge while building 1.18 quickstarts; this PR delivers what issue #1801 originally described.
This brings .NET in line for cross-SDK parity before 1.18 ships.
Three concrete gaps closed
1. Activity-level opt-in (was missing entirely)
Before this PR,
PropagationScopeonly lived onChildWorkflowTaskOptions— there was no way to propagate history when scheduling an activity. The go-sdk reference example uses exactly this pattern (SettlePaymentactivity withPropagateOwnHistory), so the cross-SDK quickstart literally couldn't be ported.PropagationScopemoved to baseWorkflowTaskOptions.WithHistoryPropagation()extension method available on bothWorkflowTaskOptionsandChildWorkflowTaskOptions.scheduleTaskAction.HistoryPropagationScopewired inWorkflowOrchestrationContext.CallActivityInternalAsync, matching the existing child-workflow path.2. Read API replaced (was lossy
FilterBy*+ minimal events)The old
PropagatedHistoryEventonly kept(EventId, Kind, Timestamp)— input/output/failure payloads were thrown away byConvertChunk, making the high-level audit/fraud-detection patterns from the Go/Python quickstarts impossible.PropagatedHistory.FilterByAppId/InstanceId/WorkflowName,PropagatedHistoryEntry,PropagatedHistoryEvent,HistoryEventKind(clean break — 1.18 unreleased).PropagatedHistory:GetWorkflows(),GetWorkflowsByName(),GetLastWorkflowByName(),GetAppIds(),GetWorkflowsByAppId(),GetWorkflowsByInstanceId().WorkflowResultclass withGetLastActivityByName(),GetActivitiesByName(),GetLastChildWorkflowByName(),GetChildWorkflowsByName()— mirrors theGetLast*ByNamerename merged in durabletask-go#105.ActivityResult(Name, Started, Completed, Failed, Input, Output, FailureDetails)record — matches the Go SDK'sActivityResultstruct and Python'sActivityResultdataclass.ChildWorkflowResultwith the equivalent shape.3. Event payload preserved internally
ConvertChunknow walks the rawHistoryEvents, matchesTaskScheduledagainstTaskCompleted/TaskFailedbytaskScheduledId(SDK retries reuseTaskExecutionIdso we match on the scheduling event ID, matching Go/Python semantics), and produces fully-populatedActivityResult/ChildWorkflowResultrecords. The protoHistoryEventtype is never exposed publicly — resolution happens at construction time insideDapr.Workflow.Additional surface
PropagationNotFoundExceptionfor missing-name lookups (mirrors Python'sPropagationNotFoundError, Go's error returns).static WorkflowHistory.PropagateLineage()/PropagateOwnHistory()factory helpers — call-site parity with go-sdk'sworkflow.PropagateLineage()/workflow.PropagateOwnHistory(). Both forms are supported:options.WithHistoryPropagation(WorkflowHistory.PropagateLineage())≡options.WithHistoryPropagation(HistoryPropagationScope.Lineage).Usage example (matches the go-sdk reference)
```csharp
// Parent workflow propagates lineage to a child workflow and own-history to an activity
var fraudResult = await context.CallChildWorkflowAsync(
nameof(FraudDetection),
input: req,
options: new ChildWorkflowTaskOptions()
.WithHistoryPropagation(WorkflowHistory.PropagateLineage()));
await context.CallActivityAsync(
nameof(SettlePayment),
input: req,
options: new WorkflowTaskOptions()
.WithHistoryPropagation(WorkflowHistory.PropagateOwnHistory()));
// Receive side — query specific events by name
var history = context.GetPropagatedHistory();
var merchant = history!
.GetLastWorkflowByName("MerchantCheckout")
.GetLastActivityByName("ValidateMerchant");
if (merchant.Completed) { /* inspect merchant.Output */ }
```
Non-goals
ScheduleTaskAction.historyPropagationScopefield 6,CreateChildWorkflowAction.historyPropagationScopefield 6); we just had to wire the activity path.Breaking changes (1.18 unreleased)
The propagation API shipped in #1802/#1818 has not been released. This PR replaces it cleanly rather than carrying
[Obsolete]shims for an API that has no users yet. Removed types:PropagatedHistoryEntry,PropagatedHistoryEvent,HistoryEventKind,PropagatedHistory.FilterByAppId,FilterByInstanceId,FilterByWorkflowName.Test plan
WorkflowHistoryPropagationTests.csrewritten end-to-end (28 tests): activity resolution (completed/failed/pending/retried), child-workflow resolution, chain helpers (GetAppIds, GetLastWorkflowByName, GetWorkflowsByName, throws on missing), scheduling option records (both base + derived), factory helpers, activity-level + child-workflow outbound action scope wiring.HistoryPropagationWorkflowTests.cs(integration) updated toGetWorkflows().Count.dotnet build+dotnet testto be verified by CI — couldn't build locally (no .NET 10 SDK on dev machine). Sweep confirmed no stale references to removed types insrc/ortest/.References
GetLast*ByNamerename: dapr/durabletask-go#105cc @cicoyle @WhitWaldo