Skip to content

Commit d308c3b

Browse files
GarrettBeattyclaude
andcommitted
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<T> 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<T> expanded to the full JS/Python surface: adds Started, GetErrors(), HasFailure, SuccessCount, FailureCount, StartedCount, TotalCount. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 369a029 commit d308c3b

4 files changed

Lines changed: 87 additions & 26 deletions

File tree

Docs/durable-execution-design.md

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1279,10 +1279,9 @@ public class CallbackConfig
12791279
/// </summary>
12801280
public TimeSpan HeartbeatTimeout { get; set; } = TimeSpan.Zero;
12811281

1282-
/// <summary>
1283-
/// Custom serializer for callback result.
1284-
/// </summary>
1285-
public ICheckpointSerializer? Serializer { get; set; }
1282+
// Note: there is no Serializer property here. Custom serializers are
1283+
// supplied via the AOT-safe CreateCallbackAsync(..., ICheckpointSerializer<T>, ...)
1284+
// overload, matching the pattern established by StepAsync.
12861285
}
12871286

12881287
/// <summary>
@@ -1307,14 +1306,14 @@ public class InvokeConfig
13071306
public TimeSpan Timeout { get; set; } = TimeSpan.Zero;
13081307

13091308
/// <summary>
1310-
/// Custom serializer for the payload.
1309+
/// Optional tenant identifier propagated to the chained invocation.
1310+
/// Matches the tenantId field on Python/JS/Java InvokeConfig.
13111311
/// </summary>
1312-
public ICheckpointSerializer? PayloadSerializer { get; set; }
1312+
public string? TenantId { get; set; }
13131313

1314-
/// <summary>
1315-
/// Custom serializer for the result.
1316-
/// </summary>
1317-
public ICheckpointSerializer? ResultSerializer { get; set; }
1314+
// Note: payload and result serializers are supplied via the AOT-safe
1315+
// InvokeAsync(..., ICheckpointSerializer<TPayload>, ICheckpointSerializer<TResult>, ...)
1316+
// overload, matching the pattern established by StepAsync.
13181317
}
13191318

13201319
/// <summary>
@@ -1429,10 +1428,9 @@ public class CompletionConfig
14291428
/// </summary>
14301429
public class ChildContextConfig
14311430
{
1432-
/// <summary>
1433-
/// Custom serializer for the child context's return value.
1434-
/// </summary>
1435-
public ICheckpointSerializer? Serializer { get; set; }
1431+
// Note: there is no Serializer property here. Custom serializers are
1432+
// supplied via the AOT-safe RunInChildContextAsync(..., ICheckpointSerializer<T>, ...)
1433+
// overload, matching the pattern established by StepAsync.
14361434
14371435
/// <summary>
14381436
/// Operation sub-type label for observability (e.g., in test runner output).
@@ -1473,34 +1471,54 @@ public class WaitForConditionConfig<TState>
14731471
public interface IBatchResult<T>
14741472
{
14751473
/// <summary>
1476-
/// All items (succeeded and failed).
1474+
/// All items, in original index order.
14771475
/// </summary>
14781476
IReadOnlyList<IBatchItem<T>> All { get; }
14791477

14801478
/// <summary>
1481-
/// Only successful items.
1479+
/// Items whose Status is Succeeded.
14821480
/// </summary>
14831481
IReadOnlyList<IBatchItem<T>> Succeeded { get; }
14841482

14851483
/// <summary>
1486-
/// Only failed items.
1484+
/// Items whose Status is Failed.
14871485
/// </summary>
14881486
IReadOnlyList<IBatchItem<T>> Failed { get; }
14891487

14901488
/// <summary>
1491-
/// Get all successful results. Throws if any failed.
1489+
/// Items still in flight when the batch resolved (CompletionConfig short-circuit).
1490+
/// </summary>
1491+
IReadOnlyList<IBatchItem<T>> Started { get; }
1492+
1493+
/// <summary>
1494+
/// Get all successful results in original index order. Throws if any failed.
14921495
/// </summary>
14931496
IReadOnlyList<T> GetResults();
14941497

14951498
/// <summary>
1496-
/// Throw an exception if any item failed.
1499+
/// Get all errors from failed items.
1500+
/// </summary>
1501+
IReadOnlyList<DurableExecutionException> GetErrors();
1502+
1503+
/// <summary>
1504+
/// Throw a single aggregated exception if any item failed.
14971505
/// </summary>
14981506
void ThrowIfError();
14991507

15001508
/// <summary>
1501-
/// Why the operation completed.
1509+
/// True if any item is in the Failed state.
1510+
/// </summary>
1511+
bool HasFailure { get; }
1512+
1513+
/// <summary>
1514+
/// Why the batch resolved.
15021515
/// </summary>
15031516
CompletionReason CompletionReason { get; }
1517+
1518+
int SuccessCount { get; }
1519+
int FailureCount { get; }
1520+
int StartedCount { get; }
1521+
int TotalCount { get; }
15041522
}
15051523

15061524
public interface IBatchItem<T>
@@ -1511,7 +1529,29 @@ public interface IBatchItem<T>
15111529
DurableExecutionException? Error { get; }
15121530
}
15131531

1514-
public enum BatchItemStatus { Succeeded, Failed, Cancelled }
1532+
/// <summary>
1533+
/// Status of an individual item in a batch result.
1534+
/// Mirrors the wire-state observed at the time the batch resolved — items still
1535+
/// running when a CompletionConfig short-circuits remain in <see cref="Started"/>.
1536+
/// </summary>
1537+
public enum BatchItemStatus
1538+
{
1539+
/// <summary>
1540+
/// The branch ran to completion and produced a result.
1541+
/// </summary>
1542+
Succeeded,
1543+
1544+
/// <summary>
1545+
/// The branch ran to completion and threw.
1546+
/// </summary>
1547+
Failed,
1548+
1549+
/// <summary>
1550+
/// The branch was still in flight when the batch's CompletionConfig
1551+
/// resolved (e.g., FirstSuccessful returned before this branch finished).
1552+
/// </summary>
1553+
Started
1554+
}
15151555
public enum CompletionReason { AllCompleted, MinSuccessfulReached, FailureToleranceExceeded }
15161556

15171557
/// <summary>

Libraries/src/Amazon.Lambda.DurableExecution/Internal/Operation.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,25 @@ internal static class OperationStatuses
137137
public const string Cancelled = "CANCELLED";
138138
public const string Ready = "READY";
139139
public const string Stopped = "STOPPED";
140+
public const string TimedOut = "TIMED_OUT";
141+
}
142+
143+
/// <summary>
144+
/// Wire-format <see cref="Operation.SubType"/> string constants. Subtypes are
145+
/// observability labels mapped from the user-facing context method that
146+
/// produced the operation. The service does not interpret them; downstream
147+
/// consumers (test runner, traces, console) display them as-is.
148+
/// </summary>
149+
internal static class OperationSubTypes
150+
{
151+
public const string Step = "Step";
152+
public const string Wait = "Wait";
153+
public const string Callback = "Callback";
154+
public const string WaitForCallback = "WaitForCallback";
155+
public const string Invoke = "Invoke";
156+
public const string WaitForCondition = "WaitForCondition";
157+
public const string Parallel = "Parallel";
158+
public const string ParallelBranch = "ParallelBranch";
159+
public const string Map = "Map";
160+
public const string MapIteration = "MapIteration";
140161
}

Libraries/src/Amazon.Lambda.DurableExecution/Internal/StepOperation.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ private async Task<T> ExecuteFunc(int attemptNumber, CancellationToken cancellat
171171
Id = OperationId,
172172
Type = OperationTypes.Step,
173173
Action = "START",
174-
SubType = "Step",
174+
SubType = OperationSubTypes.Step,
175175
Name = Name
176176
};
177177

@@ -196,7 +196,7 @@ await EnqueueAsync(new SdkOperationUpdate
196196
Id = OperationId,
197197
Type = OperationTypes.Step,
198198
Action = "SUCCEED",
199-
SubType = "Step",
199+
SubType = OperationSubTypes.Step,
200200
Name = Name,
201201
Payload = SerializeResult(result)
202202
}, cancellationToken);
@@ -233,7 +233,7 @@ await EnqueueAsync(new SdkOperationUpdate
233233
Id = OperationId,
234234
Type = OperationTypes.Step,
235235
Action = "RETRY",
236-
SubType = "Step",
236+
SubType = OperationSubTypes.Step,
237237
Name = Name,
238238
Error = ToSdkError(ex),
239239
StepOptions = new SdkStepOptions { NextAttemptDelaySeconds = delaySeconds }
@@ -248,7 +248,7 @@ await EnqueueAsync(new SdkOperationUpdate
248248
Id = OperationId,
249249
Type = OperationTypes.Step,
250250
Action = "FAIL",
251-
SubType = "Step",
251+
SubType = OperationSubTypes.Step,
252252
Name = Name,
253253
Error = ToSdkError(ex)
254254
}, cancellationToken);

Libraries/src/Amazon.Lambda.DurableExecution/Internal/WaitOperation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ await EnqueueAsync(new SdkOperationUpdate
4848
Id = OperationId,
4949
Type = OperationTypes.Wait,
5050
Action = "START",
51-
SubType = "Wait",
51+
SubType = OperationSubTypes.Wait,
5252
Name = Name,
5353
WaitOptions = new SdkWaitOptions { WaitSeconds = _waitSeconds }
5454
}, cancellationToken);

0 commit comments

Comments
 (0)