durable-workflow 2.12.3__tar.gz → 2.12.4__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 (170) hide show
  1. {durable_workflow-2.12.3/src/durable_workflow.egg-info → durable_workflow-2.12.4}/PKG-INFO +9 -3
  2. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/README.md +8 -2
  3. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/pyproject.toml +7 -3
  4. durable_workflow-2.12.4/src/durable_workflow/server_compatibility.json +7 -0
  5. {durable_workflow-2.12.3 → durable_workflow-2.12.4/src/durable_workflow.egg-info}/PKG-INFO +9 -3
  6. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow.egg-info/SOURCES.txt +1 -0
  7. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_release_metadata.py +183 -0
  8. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/LICENSE +0 -0
  9. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/setup.cfg +0 -0
  10. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/__init__.py +0 -0
  11. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_activity_lease.py +0 -0
  12. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_activity_process.py +0 -0
  13. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_avro.py +0 -0
  14. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_cancellation_scope.py +0 -0
  15. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_cancellation_scope_history.py +0 -0
  16. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_cooperative_cancellation.py +0 -0
  17. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_failure_reporting.py +0 -0
  18. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_metric_routes.py +0 -0
  19. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_poll_transport.py +0 -0
  20. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_prepared_local_activity.py +0 -0
  21. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_query_task_authority.py +0 -0
  22. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/_sticky_workflow_cache.py +0 -0
  23. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/activity.py +0 -0
  24. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/auth_composition.py +0 -0
  25. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/cancellation.py +0 -0
  26. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/client.py +0 -0
  27. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/errors.py +0 -0
  28. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/external_storage.py +0 -0
  29. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/external_task_input.py +0 -0
  30. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/external_task_result.py +0 -0
  31. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/history_bundle_verify.py +0 -0
  32. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/interceptors.py +0 -0
  33. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/invocable.py +0 -0
  34. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/metrics.py +0 -0
  35. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/nexus.py +0 -0
  36. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/py.typed +0 -0
  37. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/python_conformance.py +0 -0
  38. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/replay_conformance.py +0 -0
  39. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/replay_verify.py +0 -0
  40. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/retry_policy.py +0 -0
  41. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/serializer.py +0 -0
  42. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/sync.py +0 -0
  43. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/telemetry.py +0 -0
  44. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/telemetry_carrier.py +0 -0
  45. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/testing.py +0 -0
  46. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/worker.py +0 -0
  47. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/worker_session.py +0 -0
  48. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/workflow.py +0 -0
  49. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow/workflow_updates_conformance.py +0 -0
  50. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
  51. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow.egg-info/entry_points.txt +0 -0
  52. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow.egg-info/requires.txt +0 -0
  53. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/src/durable_workflow.egg-info/top_level.txt +0 -0
  54. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_activity_cancellation_policies.py +0 -0
  55. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_activity_context.py +0 -0
  56. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_activity_heartbeat_authority.py +0 -0
  57. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_activity_lease_authority.py +0 -0
  58. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_activity_lease_renewal.py +0 -0
  59. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_activity_lease_worker.py +0 -0
  60. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_activity_process.py +0 -0
  61. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_activity_retry_info.py +0 -0
  62. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_auth_composition.py +0 -0
  63. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_avro_value_benchmark.py +0 -0
  64. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_avro_value_protocol.py +0 -0
  65. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cancellation_context.py +0 -0
  66. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cancellation_remaining_time.py +0 -0
  67. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cancellation_scope_admission.py +0 -0
  68. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cancellation_scope_authoring.py +0 -0
  69. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cancellation_scope_boundary.py +0 -0
  70. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cancellation_scope_opening.py +0 -0
  71. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_child_workflow_policies.py +0 -0
  72. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_ci_checkout.py +0 -0
  73. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_ci_integration_endpoint.py +0 -0
  74. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_client.py +0 -0
  75. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_client_telemetry.py +0 -0
  76. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_client_timeouts.py +0 -0
  77. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_codec_regression_corpus.py +0 -0
  78. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_committed_cancellation_scope_descendants.py +0 -0
  79. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_committed_cancellation_scope_groups.py +0 -0
  80. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_committed_cancellation_scope_history.py +0 -0
  81. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_committed_cancellation_scope_replay.py +0 -0
  82. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_condition_replay_drift.py +0 -0
  83. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_control_plane_parity_fixtures.py +0 -0
  84. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cooperative_cancellation.py +0 -0
  85. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cooperative_cancellation_client.py +0 -0
  86. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cooperative_cancellation_worker.py +0 -0
  87. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_cooperative_remote_worker.py +0 -0
  88. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_docstring_cross_references.py +0 -0
  89. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_durable_selection.py +0 -0
  90. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_empty_callback_failure.py +0 -0
  91. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_errors.py +0 -0
  92. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_external_storage.py +0 -0
  93. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_external_task_input.py +0 -0
  94. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_external_task_result.py +0 -0
  95. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_failure_reporting.py +0 -0
  96. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_generator_lifecycle.py +0 -0
  97. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_golden_history_replay.py +0 -0
  98. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_history_bundle_schema2.py +0 -0
  99. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_history_bundle_verify.py +0 -0
  100. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_history_event_contract.py +0 -0
  101. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_invocable.py +0 -0
  102. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_local_failure_reporting.py +0 -0
  103. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_message_streams.py +0 -0
  104. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_metric_routes.py +0 -0
  105. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_metrics.py +0 -0
  106. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_nexus_durable_wait.py +0 -0
  107. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_nexus_namespace.py +0 -0
  108. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_nexus_wire.py +0 -0
  109. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_order_processing_example.py +0 -0
  110. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_parallel_completion_order_replay.py +0 -0
  111. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_parallel_saga_authoring.py +0 -0
  112. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_patch_history.py +0 -0
  113. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_pending_operation_replay.py +0 -0
  114. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_poll_failure_backoff.py +0 -0
  115. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_poll_transport.py +0 -0
  116. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_prepared_local_activity_client.py +0 -0
  117. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_prepared_local_activity_groups.py +0 -0
  118. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_prepared_local_activity_policies.py +0 -0
  119. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_prepared_local_activity_worker.py +0 -0
  120. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_public_boundary_scanner.py +0 -0
  121. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_pypi_project_surface.py +0 -0
  122. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_python_conformance.py +0 -0
  123. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_queries.py +0 -0
  124. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_query_task_authority.py +0 -0
  125. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_query_timeout_retry.py +0 -0
  126. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_readme_quickstart.py +0 -0
  127. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_redrive_replay.py +0 -0
  128. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_registration_ambiguity.py +0 -0
  129. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_replay.py +0 -0
  130. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_replay_clock.py +0 -0
  131. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_replay_conformance.py +0 -0
  132. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_replay_regression_corpus.py +0 -0
  133. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_replay_verify.py +0 -0
  134. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_retry_policy.py +0 -0
  135. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_run_cancellation_scope_replay.py +0 -0
  136. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_runtime_external_payload_transport.py +0 -0
  137. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_schedules.py +0 -0
  138. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_scoped_run_cancellation_context.py +0 -0
  139. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_sdk_metrics_contract.py +0 -0
  140. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_serializer.py +0 -0
  141. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_signals.py +0 -0
  142. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_sleep.py +0 -0
  143. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_standalone_activity_client.py +0 -0
  144. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_sticky_execution.py +0 -0
  145. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_storage_admission.py +0 -0
  146. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_sync.py +0 -0
  147. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_sync_activities.py +0 -0
  148. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_telemetry.py +0 -0
  149. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_telemetry_carrier.py +0 -0
  150. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_testing_failures.py +0 -0
  151. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_testing_harness.py +0 -0
  152. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_testing_nexus.py +0 -0
  153. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_threaded_heartbeat_example.py +0 -0
  154. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_tls_trust.py +0 -0
  155. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_transport_diagnostics.py +0 -0
  156. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_update_signal_condition_replay.py +0 -0
  157. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_updates.py +0 -0
  158. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_wait_condition.py +0 -0
  159. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_worker.py +0 -0
  160. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_worker_deregistration.py +0 -0
  161. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_worker_observation.py +0 -0
  162. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_worker_session.py +0 -0
  163. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_worker_telemetry_wire.py +0 -0
  164. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_workflow_entry_arguments.py +0 -0
  165. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_workflow_entrypoint_validation.py +0 -0
  166. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_workflow_handler_validation.py +0 -0
  167. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_workflow_identity.py +0 -0
  168. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_workflow_result_timeout.py +0 -0
  169. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/tests/test_workflow_streams.py +0 -0
  170. {durable_workflow-2.12.3 → durable_workflow-2.12.4}/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.3
3
+ Version: 2.12.4
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
@@ -160,8 +160,14 @@ portable between both runtime choices.
160
160
  ## Compatibility
161
161
 
162
162
  Stable `2.x` SDK releases follow semantic versioning and negotiate runtime
163
- capabilities with Server at startup. Use stable `2.x` SDK and Server channels
164
- for new applications. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
163
+ capabilities with Server at startup. The installed package includes
164
+ `durable_workflow/server_compatibility.json`, matching the release declarations in
165
+ `pyproject.toml`. Its `supported-server-release-baselines` maps each supported
166
+ Server major to its minimum stable release. Readers validate every entry before
167
+ selection and refuse missing majors, malformed entries, or a contradiction with
168
+ the unchanged legacy `supported-server-versions` 2.x baseline. An absent map
169
+ retains legacy reader behavior. This declaration does not replace protocol or
170
+ capability negotiation. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
165
171
  documents protocol and upgrade guarantees.
166
172
 
167
173
  ## Worker protocol and cooperative cancellation
@@ -113,8 +113,14 @@ portable between both runtime choices.
113
113
  ## Compatibility
114
114
 
115
115
  Stable `2.x` SDK releases follow semantic versioning and negotiate runtime
116
- capabilities with Server at startup. Use stable `2.x` SDK and Server channels
117
- for new applications. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
116
+ capabilities with Server at startup. The installed package includes
117
+ `durable_workflow/server_compatibility.json`, matching the release declarations in
118
+ `pyproject.toml`. Its `supported-server-release-baselines` maps each supported
119
+ Server major to its minimum stable release. Readers validate every entry before
120
+ selection and refuse missing majors, malformed entries, or a contradiction with
121
+ the unchanged legacy `supported-server-versions` 2.x baseline. An absent map
122
+ retains legacy reader behavior. This declaration does not replace protocol or
123
+ capability negotiation. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
118
124
  documents protocol and upgrade guarantees.
119
125
 
120
126
  ## Worker protocol and cooperative cancellation
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "durable-workflow"
7
- version = "2.12.3"
7
+ version = "2.12.4"
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"
@@ -79,9 +79,10 @@ durable-workflow-replay-conformance = "durable_workflow.replay_conformance:main"
79
79
  durable-workflow-workflow-updates-conformance = "durable_workflow.workflow_updates_conformance:main"
80
80
 
81
81
  [tool.durable-workflow]
82
- product-train = "2.12.3"
83
- registry-version = "2.12.3"
82
+ product-train = "2.12.4"
83
+ registry-version = "2.12.4"
84
84
  supported-server-versions = "2.8.0"
85
+ supported-server-release-baselines = { "2" = "2.8.0", "3" = "3.0.0" }
85
86
  worker-protocol-version = "1.20"
86
87
  control-plane-version = "2"
87
88
  durable-selection = true
@@ -106,6 +107,9 @@ saga-cancellation = "cooperative"
106
107
  [tool.setuptools.packages.find]
107
108
  where = ["src"]
108
109
 
110
+ [tool.setuptools.package-data]
111
+ durable_workflow = ["server_compatibility.json"]
112
+
109
113
  [tool.pytest.ini_options]
110
114
  testpaths = ["tests"]
111
115
  asyncio_mode = "auto"
@@ -0,0 +1,7 @@
1
+ {
2
+ "supported-server-versions": "2.8.0",
3
+ "supported-server-release-baselines": {
4
+ "2": "2.8.0",
5
+ "3": "3.0.0"
6
+ }
7
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.12.3
3
+ Version: 2.12.4
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
@@ -160,8 +160,14 @@ portable between both runtime choices.
160
160
  ## Compatibility
161
161
 
162
162
  Stable `2.x` SDK releases follow semantic versioning and negotiate runtime
163
- capabilities with Server at startup. Use stable `2.x` SDK and Server channels
164
- for new applications. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
163
+ capabilities with Server at startup. The installed package includes
164
+ `durable_workflow/server_compatibility.json`, matching the release declarations in
165
+ `pyproject.toml`. Its `supported-server-release-baselines` maps each supported
166
+ Server major to its minimum stable release. Readers validate every entry before
167
+ selection and refuse missing majors, malformed entries, or a contradiction with
168
+ the unchanged legacy `supported-server-versions` 2.x baseline. An absent map
169
+ retains legacy reader behavior. This declaration does not replace protocol or
170
+ capability negotiation. The [compatibility guide](https://durable-workflow.com/docs/2.0/compatibility/)
165
171
  documents protocol and upgrade guarantees.
166
172
 
167
173
  ## Worker protocol and cooperative cancellation
@@ -33,6 +33,7 @@ src/durable_workflow/replay_conformance.py
33
33
  src/durable_workflow/replay_verify.py
34
34
  src/durable_workflow/retry_policy.py
35
35
  src/durable_workflow/serializer.py
36
+ src/durable_workflow/server_compatibility.json
36
37
  src/durable_workflow/sync.py
37
38
  src/durable_workflow/telemetry.py
38
39
  src/durable_workflow/telemetry_carrier.py
@@ -1,7 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import io
3
4
  import json
4
5
  import sys
6
+ import tarfile
7
+ import zipfile
8
+ from dataclasses import replace
5
9
  from pathlib import Path
6
10
  from types import ModuleType
7
11
 
@@ -9,7 +13,9 @@ import pytest
9
13
  from scripts import check_release_metadata
10
14
  from scripts.check_release_metadata import ReleaseMetadataError, SourceMetadata
11
15
  from scripts.release_compatibility import (
16
+ CompatibilityContractError,
12
17
  declared_runtime_protocol_version,
18
+ validate_server_release_baselines,
13
19
  )
14
20
 
15
21
  from durable_workflow.client import PROTOCOL_VERSION
@@ -48,12 +54,187 @@ def test_worker_release_identity_matches_supported_server_and_protocol() -> None
48
54
  assert release["worker-protocol-version"] == PROTOCOL_VERSION == "1.20"
49
55
  assert release["durable-selection"] is True
50
56
  assert release["durable-selection-minimum-worker-protocol-version"] == "1.19"
57
+ baselines = validate_server_release_baselines(
58
+ release["supported-server-versions"], release["supported-server-release-baselines"]
59
+ )
60
+ assert baselines == {"2": release["supported-server-versions"], "3": "3.0.0"}
61
+ record = (REPO_ROOT / "src/durable_workflow/server_compatibility.json").read_bytes()
62
+ assert record == (json.dumps({
63
+ "supported-server-versions": release["supported-server-versions"],
64
+ "supported-server-release-baselines": baselines,
65
+ }, indent=2) + "\n").encode()
51
66
 
52
67
  assert declared_runtime_protocol_version(
53
68
  (REPO_ROOT / "src" / "durable_workflow" / "client.py").read_text(encoding="utf-8")
54
69
  ) == "1.20"
55
70
 
56
71
 
72
+ @pytest.mark.parametrize("baselines", [
73
+ None, {}, [], "3.0.0", {2: "2.8.0"}, {"02": "2.8.0"},
74
+ {"0": "0.1.0"}, {"2": "2.8.0", "3": 3},
75
+ {"2": "2.8.0", "3": "3.0.0-rc.1"},
76
+ {"2": "2.8.0", "3": "3.00.0"},
77
+ {"2": "2.8.0", "4": "3.0.0"},
78
+ {"2": "2.9.0", "3": "3.0.0"}, {"3": "3.0.0"},
79
+ ])
80
+ def test_server_baselines_refuse_malformed_or_contradictory_maps(baselines: object) -> None:
81
+ with pytest.raises(CompatibilityContractError):
82
+ validate_server_release_baselines("2.8.0", baselines)
83
+
84
+
85
+ def test_server_baselines_do_not_imply_unlisted_major_support() -> None:
86
+ baselines = validate_server_release_baselines("2.8.0", {"2": "2.8.0", "3": "3.0.0"})
87
+ assert "4" not in baselines
88
+
89
+
90
+ COMPATIBILITY_RECORD = b'{"supported-server-versions":"2.0.0"}\n'
91
+
92
+
93
+ def write_distributions(
94
+ dist: Path,
95
+ source: SourceMetadata,
96
+ *,
97
+ wheel_record: bytes | None,
98
+ sdist_record: bytes | None,
99
+ ) -> None:
100
+ headers = (
101
+ f"Name: {source.name}\nVersion: {source.registry_version}\nSummary: {source.summary}\n"
102
+ "Description-Content-Type: text/markdown\n"
103
+ + "".join(f"Classifier: {value}\n" for value in source.classifiers)
104
+ + "\n" + source.readme
105
+ ).encode()
106
+ with zipfile.ZipFile(dist / "sdk.whl", "w") as archive:
107
+ archive.writestr("sdk.dist-info/METADATA", headers)
108
+ if wheel_record is not None:
109
+ archive.writestr("durable_workflow/server_compatibility.json", wheel_record)
110
+ members = {"sdk/PKG-INFO": headers, "sdk/README.md": source.readme.encode()}
111
+ if sdist_record is not None:
112
+ members["sdk/src/durable_workflow/server_compatibility.json"] = sdist_record
113
+ with tarfile.open(dist / "sdk.tar.gz", "w:gz") as archive:
114
+ for path, content in members.items():
115
+ member = tarfile.TarInfo(path)
116
+ member.size = len(content)
117
+ archive.addfile(member, io.BytesIO(content))
118
+
119
+
120
+ @pytest.mark.parametrize(("wheel_record", "sdist_record", "refusal"), [
121
+ (COMPATIBILITY_RECORD, COMPATIBILITY_RECORD, None),
122
+ (b"{}\n", COMPATIBILITY_RECORD, "wheel Server compatibility record differs"),
123
+ (COMPATIBILITY_RECORD, b"{}\n", "sdist Server compatibility record differs"),
124
+ (None, COMPATIBILITY_RECORD, "wheel Server compatibility record differs"),
125
+ (COMPATIBILITY_RECORD, None, "sdist must contain one Server compatibility record"),
126
+ ])
127
+ def test_built_packages_must_retain_exact_server_compatibility(
128
+ tmp_path: Path,
129
+ wheel_record: bytes | None,
130
+ sdist_record: bytes | None,
131
+ refusal: str | None,
132
+ ) -> None:
133
+ source = replace(source_metadata(), server_compatibility=COMPATIBILITY_RECORD)
134
+ write_distributions(tmp_path, source, wheel_record=wheel_record, sdist_record=sdist_record)
135
+ if refusal is None:
136
+ check_release_metadata.verify_built_distributions(tmp_path, source)
137
+ else:
138
+ with pytest.raises(ReleaseMetadataError, match=refusal):
139
+ check_release_metadata.verify_built_distributions(tmp_path, source)
140
+
141
+
142
+ @pytest.mark.parametrize(("wheel_record", "sdist_record", "refusal"), [
143
+ (None, None, None),
144
+ (COMPATIBILITY_RECORD, None, "wheel Server compatibility record is absent from exact source"),
145
+ (None, COMPATIBILITY_RECORD, "sdist Server compatibility record is absent from exact source"),
146
+ ])
147
+ def test_legacy_source_packages_must_not_carry_unverified_server_compatibility(
148
+ tmp_path: Path,
149
+ wheel_record: bytes | None,
150
+ sdist_record: bytes | None,
151
+ refusal: str | None,
152
+ ) -> None:
153
+ source = source_metadata()
154
+ assert source.server_compatibility is None
155
+ write_distributions(tmp_path, source, wheel_record=wheel_record, sdist_record=sdist_record)
156
+ if refusal is None:
157
+ check_release_metadata.verify_built_distributions(tmp_path, source)
158
+ else:
159
+ with pytest.raises(ReleaseMetadataError, match=refusal):
160
+ check_release_metadata.verify_built_distributions(tmp_path, source)
161
+
162
+
163
+ EXACT_SOURCE_RECORD = (
164
+ json.dumps({
165
+ "supported-server-versions": "2.8.0",
166
+ "supported-server-release-baselines": {"2": "2.8.0", "3": "3.0.0"},
167
+ }, indent=2) + "\n"
168
+ ).encode()
169
+
170
+
171
+ @pytest.mark.parametrize(("baselines", "record", "refusal"), [
172
+ (None, None, None),
173
+ ('{ "2" = "2.8.0", "3" = "3.0.0" }', EXACT_SOURCE_RECORD, None),
174
+ (None, EXACT_SOURCE_RECORD, "record is present without supported-server-release-baselines"),
175
+ ('{ "2" = "2.8.0", "3" = "3.0.0" }', None, "requires the packaged Server compatibility record"),
176
+ ('{ "2" = "2.8.0", "3" = "3.0.0" }', EXACT_SOURCE_RECORD.replace(b"3.0.0", b"3.1.0"),
177
+ "packaged Server compatibility record differs from pyproject.toml"),
178
+ ('{ "2" = "2.8.0", "3" = "3.0.0" }', EXACT_SOURCE_RECORD.rstrip(b"\n"),
179
+ "packaged Server compatibility record differs from pyproject.toml"),
180
+ ('{ "2" = "2.8.0" }', EXACT_SOURCE_RECORD, "packaged Server compatibility record differs from pyproject.toml"),
181
+ ('{ "2" = "2.8.0", "3" = "3.0.0-rc.1" }', EXACT_SOURCE_RECORD, "stable release of its keyed major"),
182
+ ('{ "3" = "3.0.0" }', EXACT_SOURCE_RECORD, "contradict or omit the legacy baseline"),
183
+ ('{ "2" = "2.9.0", "3" = "3.0.0" }', EXACT_SOURCE_RECORD, "contradict or omit the legacy baseline"),
184
+ ("{}", EXACT_SOURCE_RECORD, "non-empty major-to-release map"),
185
+ ('"3.0.0"', EXACT_SOURCE_RECORD, "non-empty major-to-release map"),
186
+ ])
187
+ def test_release_metadata_loader_binds_server_compatibility_to_exact_source(
188
+ monkeypatch: pytest.MonkeyPatch,
189
+ baselines: str | None,
190
+ record: bytes | None,
191
+ refusal: str | None,
192
+ ) -> None:
193
+ commit = "c" * 40
194
+ baseline_line = "" if baselines is None else f"supported-server-release-baselines = {baselines}\n"
195
+ pyproject = f"""
196
+ [project]
197
+ name = "durable-workflow"
198
+ version = "2.12.4"
199
+ description = "Python SDK for Durable Workflow 2.0"
200
+ readme = "README.md"
201
+ classifiers = ["Programming Language :: Python :: 3"]
202
+
203
+ [tool.durable-workflow]
204
+ product-train = "2.12.4"
205
+ registry-version = "2.12.4"
206
+ supported-server-versions = "2.8.0"
207
+ {baseline_line}worker-protocol-version = "1.20"
208
+ """.encode()
209
+ record_path = "src/durable_workflow/server_compatibility.json"
210
+
211
+ def git(*arguments: str) -> bytes:
212
+ if arguments[0] == "rev-parse":
213
+ return f"{commit}\n".encode()
214
+ if arguments[0] == "ls-tree":
215
+ assert arguments[1:] == ("--name-only", commit, "--", record_path)
216
+ return b"" if record is None else f"{record_path}\n".encode()
217
+ assert arguments[0] == "show"
218
+ if arguments[1] == f"{commit}:pyproject.toml":
219
+ return pyproject
220
+ if arguments[1] == f"{commit}:README.md":
221
+ return b"# Durable Workflow\n\nBuild durable Python workflows.\n"
222
+ if arguments[1] == f"{commit}:src/durable_workflow/client.py":
223
+ return b'PROTOCOL_VERSION = "1.20"\n'
224
+ assert arguments[1] == f"{commit}:{record_path}"
225
+ assert record is not None
226
+ return record
227
+
228
+ monkeypatch.setattr(check_release_metadata, "_git", git)
229
+
230
+ if refusal is not None:
231
+ with pytest.raises(ReleaseMetadataError, match=refusal):
232
+ check_release_metadata.load_source_metadata("HEAD")
233
+ return
234
+ source = check_release_metadata.load_source_metadata("HEAD")
235
+ assert source.server_compatibility == record
236
+
237
+
57
238
  def pypi_json(source: SourceMetadata, **overrides: object) -> bytes:
58
239
  info: dict[str, object] = {
59
240
  "name": source.name,
@@ -116,6 +297,8 @@ worker-protocol-version = "1.19"
116
297
  def git(*arguments: str) -> bytes:
117
298
  if arguments[0] == "rev-parse":
118
299
  return f"{commit}\n".encode()
300
+ if arguments[0] == "ls-tree":
301
+ return b""
119
302
  assert arguments[0] == "show"
120
303
  if arguments[1].endswith(":pyproject.toml"):
121
304
  return pyproject