durable-workflow 2.12.0__tar.gz → 2.12.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. {durable_workflow-2.12.0/src/durable_workflow.egg-info → durable_workflow-2.12.1}/PKG-INFO +1 -1
  2. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/pyproject.toml +3 -3
  3. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/workflow.py +15 -1
  4. {durable_workflow-2.12.0 → durable_workflow-2.12.1/src/durable_workflow.egg-info}/PKG-INFO +1 -1
  5. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_replay_regression_corpus.py +2 -0
  6. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_wait_condition.py +41 -1
  7. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/LICENSE +0 -0
  8. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/README.md +0 -0
  9. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/setup.cfg +0 -0
  10. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/__init__.py +0 -0
  11. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_activity_lease.py +0 -0
  12. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_activity_process.py +0 -0
  13. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_avro.py +0 -0
  14. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_cancellation_scope.py +0 -0
  15. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_cancellation_scope_history.py +0 -0
  16. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_cooperative_cancellation.py +0 -0
  17. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_failure_reporting.py +0 -0
  18. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_metric_routes.py +0 -0
  19. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_poll_transport.py +0 -0
  20. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_prepared_local_activity.py +0 -0
  21. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_query_task_authority.py +0 -0
  22. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/_sticky_workflow_cache.py +0 -0
  23. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/activity.py +0 -0
  24. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/auth_composition.py +0 -0
  25. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/cancellation.py +0 -0
  26. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/client.py +0 -0
  27. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/errors.py +0 -0
  28. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/external_storage.py +0 -0
  29. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/external_task_input.py +0 -0
  30. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/external_task_result.py +0 -0
  31. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/history_bundle_verify.py +0 -0
  32. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/interceptors.py +0 -0
  33. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/invocable.py +0 -0
  34. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/metrics.py +0 -0
  35. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/nexus.py +0 -0
  36. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/py.typed +0 -0
  37. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/python_conformance.py +0 -0
  38. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/replay_conformance.py +0 -0
  39. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/replay_verify.py +0 -0
  40. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/retry_policy.py +0 -0
  41. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/serializer.py +0 -0
  42. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/sync.py +0 -0
  43. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/telemetry.py +0 -0
  44. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/telemetry_carrier.py +0 -0
  45. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/testing.py +0 -0
  46. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/worker.py +0 -0
  47. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/worker_session.py +0 -0
  48. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow/workflow_updates_conformance.py +0 -0
  49. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
  50. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
  51. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow.egg-info/entry_points.txt +0 -0
  52. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow.egg-info/requires.txt +0 -0
  53. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/src/durable_workflow.egg-info/top_level.txt +0 -0
  54. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_activity_cancellation_policies.py +0 -0
  55. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_activity_context.py +0 -0
  56. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_activity_heartbeat_authority.py +0 -0
  57. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_activity_lease_authority.py +0 -0
  58. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_activity_lease_renewal.py +0 -0
  59. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_activity_lease_worker.py +0 -0
  60. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_activity_process.py +0 -0
  61. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_activity_retry_info.py +0 -0
  62. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_auth_composition.py +0 -0
  63. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_avro_value_benchmark.py +0 -0
  64. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_avro_value_protocol.py +0 -0
  65. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cancellation_context.py +0 -0
  66. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cancellation_remaining_time.py +0 -0
  67. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cancellation_scope_admission.py +0 -0
  68. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cancellation_scope_authoring.py +0 -0
  69. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cancellation_scope_boundary.py +0 -0
  70. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cancellation_scope_opening.py +0 -0
  71. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_child_workflow_policies.py +0 -0
  72. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_ci_checkout.py +0 -0
  73. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_ci_integration_endpoint.py +0 -0
  74. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_client.py +0 -0
  75. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_client_telemetry.py +0 -0
  76. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_client_timeouts.py +0 -0
  77. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_codec_regression_corpus.py +0 -0
  78. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_committed_cancellation_scope_descendants.py +0 -0
  79. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_committed_cancellation_scope_groups.py +0 -0
  80. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_committed_cancellation_scope_history.py +0 -0
  81. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_committed_cancellation_scope_replay.py +0 -0
  82. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_condition_replay_drift.py +0 -0
  83. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_control_plane_parity_fixtures.py +0 -0
  84. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cooperative_cancellation.py +0 -0
  85. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cooperative_cancellation_client.py +0 -0
  86. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cooperative_cancellation_worker.py +0 -0
  87. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_cooperative_remote_worker.py +0 -0
  88. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_docstring_cross_references.py +0 -0
  89. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_durable_selection.py +0 -0
  90. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_errors.py +0 -0
  91. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_external_storage.py +0 -0
  92. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_external_task_input.py +0 -0
  93. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_external_task_result.py +0 -0
  94. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_failure_reporting.py +0 -0
  95. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_golden_history_replay.py +0 -0
  96. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_history_bundle_schema2.py +0 -0
  97. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_history_bundle_verify.py +0 -0
  98. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_history_event_contract.py +0 -0
  99. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_invocable.py +0 -0
  100. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_local_failure_reporting.py +0 -0
  101. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_message_streams.py +0 -0
  102. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_metric_routes.py +0 -0
  103. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_metrics.py +0 -0
  104. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_nexus_durable_wait.py +0 -0
  105. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_nexus_namespace.py +0 -0
  106. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_nexus_wire.py +0 -0
  107. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_order_processing_example.py +0 -0
  108. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_parallel_completion_order_replay.py +0 -0
  109. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_parallel_saga_authoring.py +0 -0
  110. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_patch_history.py +0 -0
  111. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_pending_operation_replay.py +0 -0
  112. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_poll_failure_backoff.py +0 -0
  113. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_poll_transport.py +0 -0
  114. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_prepared_local_activity_client.py +0 -0
  115. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_prepared_local_activity_groups.py +0 -0
  116. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_prepared_local_activity_policies.py +0 -0
  117. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_prepared_local_activity_worker.py +0 -0
  118. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_public_boundary_scanner.py +0 -0
  119. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_pypi_project_surface.py +0 -0
  120. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_python_conformance.py +0 -0
  121. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_queries.py +0 -0
  122. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_query_task_authority.py +0 -0
  123. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_query_timeout_retry.py +0 -0
  124. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_readme_quickstart.py +0 -0
  125. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_redrive_replay.py +0 -0
  126. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_registration_ambiguity.py +0 -0
  127. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_release_metadata.py +0 -0
  128. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_replay.py +0 -0
  129. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_replay_clock.py +0 -0
  130. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_replay_conformance.py +0 -0
  131. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_replay_verify.py +0 -0
  132. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_retry_policy.py +0 -0
  133. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_run_cancellation_scope_replay.py +0 -0
  134. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_runtime_external_payload_transport.py +0 -0
  135. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_schedules.py +0 -0
  136. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_scoped_run_cancellation_context.py +0 -0
  137. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_sdk_metrics_contract.py +0 -0
  138. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_serializer.py +0 -0
  139. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_signals.py +0 -0
  140. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_sleep.py +0 -0
  141. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_standalone_activity_client.py +0 -0
  142. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_sticky_execution.py +0 -0
  143. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_storage_admission.py +0 -0
  144. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_sync.py +0 -0
  145. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_sync_activities.py +0 -0
  146. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_telemetry.py +0 -0
  147. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_telemetry_carrier.py +0 -0
  148. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_testing_failures.py +0 -0
  149. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_testing_harness.py +0 -0
  150. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_testing_nexus.py +0 -0
  151. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_threaded_heartbeat_example.py +0 -0
  152. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_tls_trust.py +0 -0
  153. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_transport_diagnostics.py +0 -0
  154. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_update_signal_condition_replay.py +0 -0
  155. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_updates.py +0 -0
  156. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_worker.py +0 -0
  157. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_worker_deregistration.py +0 -0
  158. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_worker_observation.py +0 -0
  159. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_worker_session.py +0 -0
  160. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_worker_telemetry_wire.py +0 -0
  161. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_workflow_entry_arguments.py +0 -0
  162. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_workflow_entrypoint_validation.py +0 -0
  163. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_workflow_handler_validation.py +0 -0
  164. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_workflow_identity.py +0 -0
  165. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_workflow_result_timeout.py +0 -0
  166. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_workflow_streams.py +0 -0
  167. {durable_workflow-2.12.0 → durable_workflow-2.12.1}/tests/test_workflow_updates_conformance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.12.0
3
+ Version: 2.12.1
4
4
  Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "durable-workflow"
7
- version = "2.12.0"
7
+ version = "2.12.1"
8
8
  description = "Python client and worker SDK for Durable Workflow Cloud and self-hosted Server"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -76,8 +76,8 @@ durable-workflow-replay-conformance = "durable_workflow.replay_conformance:main"
76
76
  durable-workflow-workflow-updates-conformance = "durable_workflow.workflow_updates_conformance:main"
77
77
 
78
78
  [tool.durable-workflow]
79
- product-train = "2.12.0"
80
- registry-version = "2.12.0"
79
+ product-train = "2.12.1"
80
+ registry-version = "2.12.1"
81
81
  supported-server-versions = "2.8.0"
82
82
  worker-protocol-version = "1.20"
83
83
  control-plane-version = "2"
@@ -4661,6 +4661,7 @@ def _replay_state(
4661
4661
  # Receiver replay uses this exact boundary so interleaved input is applied
4662
4662
  # in durable order without leaking input recorded after the terminal row.
4663
4663
  condition_wait_terminal_indexes: dict[str, int] = {}
4664
+ condition_wait_opened_indexes: dict[str, int] = {}
4664
4665
  # Selection members are bound by their durable sequence instead of being
4665
4666
  # inserted into the ordinary completion-order cursor. This is what lets a
4666
4667
  # loser complete before or after unrelated successor work without changing
@@ -5006,6 +5007,7 @@ def _replay_state(
5006
5007
  wait_id = payload.get("condition_wait_id")
5007
5008
  if not isinstance(wait_id, str) or not wait_id:
5008
5009
  continue
5010
+ condition_wait_opened_indexes.setdefault(wait_id, index)
5009
5011
 
5010
5012
  if current_wait_id is None:
5011
5013
  for receiver_index in prefix_receivers:
@@ -5070,7 +5072,14 @@ def _replay_state(
5070
5072
 
5071
5073
  for receiver_index in receivers_since_wait:
5072
5074
  if receiver_index is not None and receiver_index not in bindings:
5073
- bindings[receiver_index] = current_wait_id
5075
+ if boundary_kind == "condition":
5076
+ # A terminal row can precede the next physical reopen.
5077
+ # Later inputs admitted during the first receiver's
5078
+ # leased replay belong to that reopen, not the wait
5079
+ # already resolved by the first receiver.
5080
+ prefix_receivers.append(receiver_index)
5081
+ else:
5082
+ bindings[receiver_index] = current_wait_id
5074
5083
  current_wait_id = None
5075
5084
  receivers_since_wait = []
5076
5085
  prefix_can_bind_to_first_wait = boundary_kind == "condition"
@@ -5431,6 +5440,11 @@ def _replay_state(
5431
5440
  terminal_index = condition_wait_terminal_indexes.get(condition_wait_id)
5432
5441
  if terminal_index is not None:
5433
5442
  while pending_receivers and pending_receivers[0].history_index < terminal_index:
5443
+ receiver_wait_id = pending_receivers[0].condition_wait_id
5444
+ receiver_wait_index = (condition_wait_opened_indexes.get(receiver_wait_id)
5445
+ if receiver_wait_id is not None else None)
5446
+ if receiver_wait_index is not None and receiver_wait_index > terminal_index:
5447
+ break
5434
5448
  _apply_receiver(pending_receivers.pop(0))
5435
5449
  return
5436
5450
  target_index = next(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.12.0
3
+ Version: 2.12.1
4
4
  Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -47,6 +47,7 @@ from tests.test_update_signal_condition_replay import (
47
47
  PostConditionReceiversWorkflow,
48
48
  UpdateSignalConditionTimerWorkflow,
49
49
  )
50
+ from tests.test_wait_condition import SignalCounterUntilFinished
50
51
  from tests.test_worker import compatible_cluster_info
51
52
  from tests.test_workflow_identity import IdentityAcrossReplay, IdentityContinueAsNew
52
53
 
@@ -375,6 +376,7 @@ WORKFLOWS = [
375
376
  FencedRecoveryWorkflow,
376
377
  PagedRecordedSideEffectsWorkflow,
377
378
  PostConditionReceiversWorkflow,
379
+ SignalCounterUntilFinished,
378
380
  RecordedSideEffectWorkflow,
379
381
  SelectionAwaitMarkerWorkflow,
380
382
  SelectionCancellationQueryWorkflow,
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import pytest
4
4
 
5
5
  from durable_workflow import serializer, workflow
6
- from durable_workflow.errors import NonDeterministicReplayError
6
+ from durable_workflow.errors import NonDeterministicReplayError, QueryFailed
7
7
  from durable_workflow.workflow import (
8
8
  CompleteWorkflow,
9
9
  WaitCondition,
@@ -567,6 +567,46 @@ class TestReplayWaitCondition:
567
567
  assert outcome.commands[0].result == expected
568
568
  assert query_state(SignalCounterUntilFinished, history, [], "current") == expected
569
569
 
570
+ @pytest.mark.parametrize("receiver_sequence", [None, 1])
571
+ @pytest.mark.parametrize("changed_identity", [None, "condition_key", "condition_definition_fingerprint"])
572
+ def test_signal_during_condition_completion_keeps_its_physical_reopen(
573
+ self, receiver_sequence: int | None, changed_identity: str | None,
574
+ ) -> None:
575
+ history = [
576
+ {"event_type": "ConditionWaitOpened", "payload": {
577
+ "condition_wait_id": "wait-first", "condition_key": "done", "sequence": 1,
578
+ }},
579
+ _signal_received_event("increment", [3], workflow_sequence=receiver_sequence),
580
+ _signal_received_event("finish", [], workflow_sequence=receiver_sequence),
581
+ {"event_type": "SignalApplied", "payload": {"signal_name": "increment"}},
582
+ {"event_type": "ConditionWaitSatisfied", "payload": {
583
+ "condition_wait_id": "wait-first", "condition_key": "done", "sequence": 1,
584
+ }},
585
+ {"event_type": "ConditionWaitOpened", "payload": {
586
+ "condition_wait_id": "wait-reopened", "condition_key": "done", "sequence": 2,
587
+ }},
588
+ ]
589
+
590
+ if changed_identity is not None:
591
+ history[-1]["payload"][changed_identity] = "changed-definition"
592
+ with pytest.raises(NonDeterministicReplayError, match="ConditionWaitOpened") as captured:
593
+ replay(SignalCounterUntilFinished, history, [])
594
+ assert captured.value.workflow_sequence == 2
595
+ with pytest.raises(QueryFailed, match="ConditionWaitOpened") as query_error:
596
+ query_state(SignalCounterUntilFinished, history, [], "current")
597
+ assert isinstance(query_error.value.__cause__, NonDeterministicReplayError)
598
+ assert query_error.value.__cause__.workflow_sequence == 2
599
+ return
600
+
601
+ expected = {"count": 3, "done": True, "events": [
602
+ {"signal": "increment", "amount": 3, "count": 3}, {"signal": "finish", "count": 3},
603
+ ]}
604
+ outcome = replay(SignalCounterUntilFinished, history, [])
605
+ assert len(outcome.commands) == 1
606
+ assert isinstance(outcome.commands[0], CompleteWorkflow)
607
+ assert outcome.commands[0].result == expected
608
+ assert query_state(SignalCounterUntilFinished, history, [], "current") == expected
609
+
570
610
  def test_signal_received_before_reopened_wait_uses_next_wait_when_sequence_absent(self) -> None:
571
611
  history = [
572
612
  {