Repository navigation
[Testing]: Preserve empty callback error history without changing runtime errors #862
Description
Split from PR #795 — match replay state to the service, reference commit f8ac9b4; baseline main is d7ae95571c44b18c2a2efdef9a308b1c916c5049. This issue owns one independently implementable slice of that PR. Start a focused branch from current main and port only this slice, rather than stacking on the full PR.
Problem and reproduction
The local runner conflates two representations: detailed history may contain a present empty Error.Payload object, while SDK-facing operation state should treat an empty error object as absent diagnostics. EventError.from_dict currently drops a present empty payload by testing its truthiness. A live callback completed with an empty ErrorObject can also behave differently in memory versus a filesystem serialization round-trip.
Through the public runner/executor callback API, complete a real callback with no diagnostic fields. Inspect detailed history, history with IncludeExecutionData=False, live operation state, persisted/reloaded state, and operations reconstructed from history. Repeat with an explicit empty ErrorObject and a nonempty error control.
Scope and implementation
- Preserve the difference between omitted/null
Payloadand presentPayload={}inEventError.from_dict. - Normalize an empty live callback failure error to absent SDK diagnostics at the completion boundary.
- Project an empty payload for detailed callback-failure history without mutating live state or inventing message/type/stack fields.
- Normalize empty errors when converting failure history back into callback, step, child-context, and chained-invoke operation state; preserve meaningful error objects.
- Keep
main'sIncludeExecutionDatabehavior: omitted data must not leak payloads, andTruncatedfollows that inclusion flag.
Primary code: Execution.complete_callback_failure in execution.py, plus EventError.from_dict, Event.create_callback_event_failed, and events_to_operations in model.py.
Acceptance criteria
- Omitted/null/empty/nonempty payloads round-trip with their distinct meanings and truncation flags preserved.
- An actual empty-error callback completion produces equivalent SDK state in memory and filesystem stores, both before and after persistence.
- Detailed callback-failure history retains
Payload: {}; nondetailed history omits the payload. Runtime state remains FAILED with absent diagnostic details. - A public handler observes the stored failure on resume without repeating completed user code. Nonempty error message/type/data/stack content remains intact.
- History-to-operation reconstruction preserves the same empty-error semantics for callback, step, context, and chained-invoke failures.
- Timeout behavior is unchanged: no synthetic empty failure payload is added to a timeout, and detailed timeout history preserves
main's truncation behavior.
Reference tests and missing coverage
The empty-payload case of test_event_error_roundtrip_preserves_present_payload failed against baseline source during #795 validation. Preserve the expectation updates in test_callback_failure_history_projection_preserves_sdk_state and test_callback_timeout_history_projection_is_unchanged that retain main's data-inclusion rules.
Add the live completion regression identified in review. Directly constructing history/operation objects does not exercise the completion-boundary normalization. The new test must fail if that normalization is removed, cover both stores, and inspect both runtime state and history.
Independence
This is independent of callback updated-ID production and input acknowledgment: drive a single callback completion and inspect state/history directly, without asserting cross-invocation update counts. It also requires no replay pruning, flag projection, OTel status change, or core snapshot-copy change. OTel status/exception changes in #853 and plugin error isolation in #852 are separate concerns.
Validation and evidence
Add a regression that fails on main without this fix and passes with this slice alone. Run from the repository root:
hatch run dev-testing:test
hatch run dev-testing:typecheck
hatch run dev-testing:js-examplesUse the repository's CI-pinned Hatch version (1.16.5 at the reference) and Node.js 22+ for the JS harness; run hatch fmt --check from packages/aws-durable-execution-sdk-python-testing. Use public-handler integration tests for cross-component behavior and both memory/filesystem stores where relevant. List changed test expectations and their reasons in the implementing PR.
The service-parity expectations are carried forward from #795. This split does not independently verify them against deployed AWS Lambda; cite service evidence or mark the external assumption unverified before claiming deployed-service qualification. Shared conformance work belongs to #796 / conformance PR #131; coordinate new requirements there.
Activity
[-][Testing]: Preserve empty callback error history without changing runtime errors[/-][+][SDK/Testing]: Preserve empty errors through deserialization and history[/+][-][SDK/Testing]: Preserve empty errors through deserialization and history[/-][+][Testing]: Preserve empty callback error history without changing runtime errors[/+]