From d308c3b9da68b6fe004b55bde29feb23910d68fe Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Thu, 14 May 2026 15:25:32 -0400 Subject: [PATCH] Add cross-cutting types and align design doc with reference SDKs Lays down shared types/constants for the upcoming durable-execution context operations (Callbacks, Invoke, Parallel, Map, WaitForCondition) and updates the design doc to match decisions reached after comparing against the Python, JS, and Java reference SDKs. SDK changes: - OperationSubTypes constants class (Step, Wait, Callback, WaitForCallback, Invoke, WaitForCondition, Parallel, ParallelBranch, Map, MapIteration). Replaces hard-coded SubType literals in StepOperation and WaitOperation. - OperationStatuses.TimedOut for callback/invoke timeout handling. Design-doc alignment: - Drop Serializer field from CallbackConfig, InvokeConfig, ChildContextConfig. Custom serializers flow through AOT-safe ICheckpointSerializer overloads (matches the existing StepConfig pattern documented at line 1247). - InvokeConfig gains TenantId (matches Python/JS/Java); drops PayloadSerializer / ResultSerializer. - BatchItemStatus.Cancelled -> Started. The SDK does not synchronously cancel branches; the wire state of items still in flight when the batch resolves (e.g., FirstSuccessful short-circuit) is STARTED. Matches Python and JS. - IBatchResult expanded to the full JS/Python surface: adds Started, GetErrors(), HasFailure, SuccessCount, FailureCount, StartedCount, TotalCount. Co-Authored-By: Claude Opus 4.7 (1M context) --- Docs/durable-execution-design.md | 82 ++++++++++++++----- .../Internal/Operation.cs | 21 +++++ .../Internal/StepOperation.cs | 8 +- .../Internal/WaitOperation.cs | 2 +- 4 files changed, 87 insertions(+), 26 deletions(-) diff --git a/Docs/durable-execution-design.md b/Docs/durable-execution-design.md index 6df424c5f..402d689af 100644 --- a/Docs/durable-execution-design.md +++ b/Docs/durable-execution-design.md @@ -1279,10 +1279,9 @@ public class CallbackConfig /// public TimeSpan HeartbeatTimeout { get; set; } = TimeSpan.Zero; - /// - /// Custom serializer for callback result. - /// - public ICheckpointSerializer? Serializer { get; set; } + // Note: there is no Serializer property here. Custom serializers are + // supplied via the AOT-safe CreateCallbackAsync(..., ICheckpointSerializer, ...) + // overload, matching the pattern established by StepAsync. } /// @@ -1307,14 +1306,14 @@ public class InvokeConfig public TimeSpan Timeout { get; set; } = TimeSpan.Zero; /// - /// Custom serializer for the payload. + /// Optional tenant identifier propagated to the chained invocation. + /// Matches the tenantId field on Python/JS/Java InvokeConfig. /// - public ICheckpointSerializer? PayloadSerializer { get; set; } + public string? TenantId { get; set; } - /// - /// Custom serializer for the result. - /// - public ICheckpointSerializer? ResultSerializer { get; set; } + // Note: payload and result serializers are supplied via the AOT-safe + // InvokeAsync(..., ICheckpointSerializer, ICheckpointSerializer, ...) + // overload, matching the pattern established by StepAsync. } /// @@ -1429,10 +1428,9 @@ public class CompletionConfig /// public class ChildContextConfig { - /// - /// Custom serializer for the child context's return value. - /// - public ICheckpointSerializer? Serializer { get; set; } + // Note: there is no Serializer property here. Custom serializers are + // supplied via the AOT-safe RunInChildContextAsync(..., ICheckpointSerializer, ...) + // overload, matching the pattern established by StepAsync. /// /// Operation sub-type label for observability (e.g., in test runner output). @@ -1473,34 +1471,54 @@ public class WaitForConditionConfig public interface IBatchResult { /// - /// All items (succeeded and failed). + /// All items, in original index order. /// IReadOnlyList> All { get; } /// - /// Only successful items. + /// Items whose Status is Succeeded. /// IReadOnlyList> Succeeded { get; } /// - /// Only failed items. + /// Items whose Status is Failed. /// IReadOnlyList> Failed { get; } /// - /// Get all successful results. Throws if any failed. + /// Items still in flight when the batch resolved (CompletionConfig short-circuit). + /// + IReadOnlyList> Started { get; } + + /// + /// Get all successful results in original index order. Throws if any failed. /// IReadOnlyList GetResults(); /// - /// Throw an exception if any item failed. + /// Get all errors from failed items. + /// + IReadOnlyList GetErrors(); + + /// + /// Throw a single aggregated exception if any item failed. /// void ThrowIfError(); /// - /// Why the operation completed. + /// True if any item is in the Failed state. + /// + bool HasFailure { get; } + + /// + /// Why the batch resolved. /// CompletionReason CompletionReason { get; } + + int SuccessCount { get; } + int FailureCount { get; } + int StartedCount { get; } + int TotalCount { get; } } public interface IBatchItem @@ -1511,7 +1529,29 @@ public interface IBatchItem DurableExecutionException? Error { get; } } -public enum BatchItemStatus { Succeeded, Failed, Cancelled } +/// +/// Status of an individual item in a batch result. +/// Mirrors the wire-state observed at the time the batch resolved — items still +/// running when a CompletionConfig short-circuits remain in . +/// +public enum BatchItemStatus +{ + /// + /// The branch ran to completion and produced a result. + /// + Succeeded, + + /// + /// The branch ran to completion and threw. + /// + Failed, + + /// + /// The branch was still in flight when the batch's CompletionConfig + /// resolved (e.g., FirstSuccessful returned before this branch finished). + /// + Started +} public enum CompletionReason { AllCompleted, MinSuccessfulReached, FailureToleranceExceeded } /// diff --git a/Libraries/src/Amazon.Lambda.DurableExecution/Internal/Operation.cs b/Libraries/src/Amazon.Lambda.DurableExecution/Internal/Operation.cs index 473c7a3b2..3befbf7d8 100644 --- a/Libraries/src/Amazon.Lambda.DurableExecution/Internal/Operation.cs +++ b/Libraries/src/Amazon.Lambda.DurableExecution/Internal/Operation.cs @@ -137,4 +137,25 @@ internal static class OperationStatuses public const string Cancelled = "CANCELLED"; public const string Ready = "READY"; public const string Stopped = "STOPPED"; + public const string TimedOut = "TIMED_OUT"; +} + +/// +/// Wire-format string constants. Subtypes are +/// observability labels mapped from the user-facing context method that +/// produced the operation. The service does not interpret them; downstream +/// consumers (test runner, traces, console) display them as-is. +/// +internal static class OperationSubTypes +{ + public const string Step = "Step"; + public const string Wait = "Wait"; + public const string Callback = "Callback"; + public const string WaitForCallback = "WaitForCallback"; + public const string Invoke = "Invoke"; + public const string WaitForCondition = "WaitForCondition"; + public const string Parallel = "Parallel"; + public const string ParallelBranch = "ParallelBranch"; + public const string Map = "Map"; + public const string MapIteration = "MapIteration"; } diff --git a/Libraries/src/Amazon.Lambda.DurableExecution/Internal/StepOperation.cs b/Libraries/src/Amazon.Lambda.DurableExecution/Internal/StepOperation.cs index 54e52005d..2a0182d7e 100644 --- a/Libraries/src/Amazon.Lambda.DurableExecution/Internal/StepOperation.cs +++ b/Libraries/src/Amazon.Lambda.DurableExecution/Internal/StepOperation.cs @@ -171,7 +171,7 @@ private async Task ExecuteFunc(int attemptNumber, CancellationToken cancellat Id = OperationId, Type = OperationTypes.Step, Action = "START", - SubType = "Step", + SubType = OperationSubTypes.Step, Name = Name }; @@ -196,7 +196,7 @@ await EnqueueAsync(new SdkOperationUpdate Id = OperationId, Type = OperationTypes.Step, Action = "SUCCEED", - SubType = "Step", + SubType = OperationSubTypes.Step, Name = Name, Payload = SerializeResult(result) }, cancellationToken); @@ -233,7 +233,7 @@ await EnqueueAsync(new SdkOperationUpdate Id = OperationId, Type = OperationTypes.Step, Action = "RETRY", - SubType = "Step", + SubType = OperationSubTypes.Step, Name = Name, Error = ToSdkError(ex), StepOptions = new SdkStepOptions { NextAttemptDelaySeconds = delaySeconds } @@ -248,7 +248,7 @@ await EnqueueAsync(new SdkOperationUpdate Id = OperationId, Type = OperationTypes.Step, Action = "FAIL", - SubType = "Step", + SubType = OperationSubTypes.Step, Name = Name, Error = ToSdkError(ex) }, cancellationToken); diff --git a/Libraries/src/Amazon.Lambda.DurableExecution/Internal/WaitOperation.cs b/Libraries/src/Amazon.Lambda.DurableExecution/Internal/WaitOperation.cs index 59254827d..364ab05c3 100644 --- a/Libraries/src/Amazon.Lambda.DurableExecution/Internal/WaitOperation.cs +++ b/Libraries/src/Amazon.Lambda.DurableExecution/Internal/WaitOperation.cs @@ -48,7 +48,7 @@ await EnqueueAsync(new SdkOperationUpdate Id = OperationId, Type = OperationTypes.Wait, Action = "START", - SubType = "Wait", + SubType = OperationSubTypes.Wait, Name = Name, WaitOptions = new SdkWaitOptions { WaitSeconds = _waitSeconds } }, cancellationToken);