Skip to content

feat(workflow): align history propagation API with go-sdk#1825

Open
nelson-parente wants to merge 3 commits into
dapr:masterfrom
nelson-parente:align-history-propagation-go-sdk
Open

feat(workflow): align history propagation API with go-sdk#1825
nelson-parente wants to merge 3 commits into
dapr:masterfrom
nelson-parente:align-history-propagation-go-sdk

Conversation

@nelson-parente
Copy link
Copy Markdown

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, PropagationScope only lived on ChildWorkflowTaskOptions — there was no way to propagate history when scheduling an activity. The go-sdk reference example uses exactly this pattern (SettlePayment activity with PropagateOwnHistory), so the cross-SDK quickstart literally couldn't be ported.

  • PropagationScope moved to base WorkflowTaskOptions.
  • WithHistoryPropagation() extension method available on both WorkflowTaskOptions and ChildWorkflowTaskOptions.
  • scheduleTaskAction.HistoryPropagationScope wired in WorkflowOrchestrationContext.CallActivityInternalAsync, matching the existing child-workflow path.

2. Read API replaced (was lossy FilterBy* + minimal events)

The old PropagatedHistoryEvent only kept (EventId, Kind, Timestamp) — input/output/failure payloads were thrown away by ConvertChunk, making the high-level audit/fraud-detection patterns from the Go/Python quickstarts impossible.

  • Removed: PropagatedHistory.FilterByAppId/InstanceId/WorkflowName, PropagatedHistoryEntry, PropagatedHistoryEvent, HistoryEventKind (clean break — 1.18 unreleased).
  • New on PropagatedHistory: GetWorkflows(), GetWorkflowsByName(), GetLastWorkflowByName(), GetAppIds(), GetWorkflowsByAppId(), GetWorkflowsByInstanceId().
  • New WorkflowResult class with GetLastActivityByName(), GetActivitiesByName(), GetLastChildWorkflowByName(), GetChildWorkflowsByName() — mirrors the GetLast*ByName rename merged in durabletask-go#105.
  • New ActivityResult(Name, Started, Completed, Failed, Input, Output, FailureDetails) record — matches the Go SDK's ActivityResult struct and Python's ActivityResult dataclass.
  • New ChildWorkflowResult with the equivalent shape.

3. Event payload preserved internally

ConvertChunk now walks the raw HistoryEvents, matches TaskScheduled against TaskCompleted/TaskFailed by taskScheduledId (SDK retries reuse TaskExecutionId so we match on the scheduling event ID, matching Go/Python semantics), and produces fully-populated ActivityResult/ChildWorkflowResult records. The proto HistoryEvent type is never exposed publicly — resolution happens at construction time inside Dapr.Workflow.

Additional surface

  • PropagationNotFoundException for missing-name lookups (mirrors Python's PropagationNotFoundError, Go's error returns).
  • static WorkflowHistory.PropagateLineage() / PropagateOwnHistory() factory helpers — call-site parity with go-sdk's workflow.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

  • No example app in this PR. The cross-SDK quickstart lives in dapr/quickstarts#1310 and will be refreshed against this API once it merges.
  • No proto / runtime changes. All proto fields already exist (ScheduleTaskAction.historyPropagationScope field 6, CreateChildWorkflowAction.historyPropagationScope field 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.cs rewritten 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 to GetWorkflows().Count.
  • dotnet build + dotnet test to be verified by CI — couldn't build locally (no .NET 10 SDK on dev machine). Sweep confirmed no stale references to removed types in src/ or test/.

References

cc @cicoyle @WhitWaldo

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>
@nelson-parente nelson-parente requested review from a team as code owners May 20, 2026 21:22
…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>
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.

Add support for workflow history propagation

1 participant