From 1a1ec590819d16b67ad6143013d7963173402d04 Mon Sep 17 00:00:00 2001 From: yaythomas Date: Wed, 17 Jun 2026 15:41:28 +0000 Subject: [PATCH] fix(sdk): serialize context Error and ReplayChildren Operation.to_dict dropped Error and ReplayChildren from ContextDetails and emitted only Result. The read path (ContextDetails.from_dict) and the checkpoint update serializer already handle both fields, so serialized operation state did not round trip through Operation.to_dict. Include Error and ReplayChildren when present. A serialized child context failure now reconstructs the correct error class on replay, and a large payload child context keeps its ReplayChildren marker. The impact is confined to code that serializes operation state to feed a handler, which the local test runner does. Normal published SDK execution does not call Operation.to_dict on this path, so core runtime behavior for callers is unchanged. Closes #477 --- .../lambda_service.py | 9 ++++- .../tests/lambda_service_test.py | 39 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/lambda_service.py b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/lambda_service.py index 3fcbac9e..0843956b 100644 --- a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/lambda_service.py +++ b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/lambda_service.py @@ -914,7 +914,14 @@ def to_dict(self) -> MutableMapping[str, Any]: "InputPayload": self.execution_details.input_payload } if self.context_details: - result["ContextDetails"] = {"Result": self.context_details.result} + context_dict: MutableMapping[str, Any] = { + "Result": self.context_details.result, + } + if self.context_details.error: + context_dict["Error"] = self.context_details.error.to_dict() + if self.context_details.replay_children: + context_dict["ReplayChildren"] = self.context_details.replay_children + result["ContextDetails"] = context_dict if self.step_details: step_dict: MutableMapping[str, Any] = {"Attempt": self.step_details.attempt} if self.step_details.next_attempt_timestamp: diff --git a/packages/aws-durable-execution-sdk-python/tests/lambda_service_test.py b/packages/aws-durable-execution-sdk-python/tests/lambda_service_test.py index 4d4fd5a4..7c1885f0 100644 --- a/packages/aws-durable-execution-sdk-python/tests/lambda_service_test.py +++ b/packages/aws-durable-execution-sdk-python/tests/lambda_service_test.py @@ -2843,3 +2843,42 @@ def test_timestamp_converter_millisecond_boundaries(): # endregion + + +def test_operation_to_dict_serializes_context_error_and_replay_children(): + """Operation.to_dict includes Error and ReplayChildren in ContextDetails.""" + op = Operation( + operation_id="ctx-1", + operation_type=OperationType.CONTEXT, + status=OperationStatus.FAILED, + context_details=ContextDetails( + replay_children=True, + result=None, + error=ErrorObject( + message="boom", + type="ChildContextError", + data=None, + stack_trace=None, + ), + ), + ) + + context = op.to_dict()["ContextDetails"] + + assert context["Error"]["ErrorType"] == "ChildContextError" + assert context["Error"]["ErrorMessage"] == "boom" + assert context["ReplayChildren"] is True + + +def test_operation_to_dict_omits_absent_context_error_and_replay_children(): + """Operation.to_dict omits Error and ReplayChildren when they are not set.""" + op = Operation( + operation_id="ctx-2", + operation_type=OperationType.CONTEXT, + status=OperationStatus.SUCCEEDED, + context_details=ContextDetails(result='"hello"'), + ) + + context = op.to_dict()["ContextDetails"] + + assert context == {"Result": '"hello"'}