durable-workflow 2.13.0__tar.gz → 2.13.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.
- {durable_workflow-2.13.0/src/durable_workflow.egg-info → durable_workflow-2.13.1}/PKG-INFO +1 -1
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/pyproject.toml +3 -3
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/workflow.py +13 -3
- {durable_workflow-2.13.0 → durable_workflow-2.13.1/src/durable_workflow.egg-info}/PKG-INFO +1 -1
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_remaining_time.py +19 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_history_event_contract.py +26 -1
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_replay_clock.py +41 -1
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/LICENSE +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/README.md +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/setup.cfg +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_activity_lease.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_activity_process.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_cancellation_scope.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_cancellation_scope_history.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_cooperative_cancellation.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_failure_reporting.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_metric_routes.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_poll_transport.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_prepared_local_activity.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_query_task_authority.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_sticky_workflow_cache.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/cancellation.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/client.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/nexus.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/python_conformance.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/replay_conformance.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/server_compatibility.json +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/telemetry.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/telemetry_carrier.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/worker.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/worker_session.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/worker_status.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/workflow_updates_conformance.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_cancellation_policies.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_context.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_heartbeat_authority.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_lease_authority.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_lease_renewal.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_lease_worker.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_process.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_retry_info.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_auth_composition.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_avro_value_benchmark.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_avro_value_protocol.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_context.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_scope_admission.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_scope_authoring.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_scope_boundary.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_scope_opening.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_child_workflow_policies.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_ci_checkout.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_ci_integration_endpoint.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_client.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_client_telemetry.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_client_timeouts.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_codec_regression_corpus.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_committed_cancellation_scope_descendants.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_committed_cancellation_scope_groups.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_committed_cancellation_scope_history.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_committed_cancellation_scope_replay.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_condition_replay_drift.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cooperative_cancellation.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cooperative_cancellation_client.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cooperative_cancellation_worker.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cooperative_remote_worker.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_docstring_cross_references.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_durable_selection.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_empty_callback_failure.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_errors.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_external_storage.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_external_task_input.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_external_task_result.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_failure_reporting.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_generator_lifecycle.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_history_bundle_pruned.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_history_bundle_schema2.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_invocable.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_local_failure_reporting.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_message_streams.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_metric_routes.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_metrics.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_nexus_durable_wait.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_nexus_namespace.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_nexus_wire.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_parallel_completion_order_replay.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_parallel_saga_authoring.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_patch_history.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_pending_operation_replay.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_poll_failure_backoff.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_poll_transport.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_prepared_local_activity_client.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_prepared_local_activity_groups.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_prepared_local_activity_policies.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_prepared_local_activity_worker.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_pypi_project_surface.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_python_conformance.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_queries.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_query_task_authority.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_query_timeout_retry.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_redrive_replay.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_registration_ambiguity.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_release_metadata.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_replay.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_replay_conformance.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_replay_regression_corpus.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_replay_verify.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_retry_policy.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_run_cancellation_scope_replay.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_runtime_external_payload_transport.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_schedules.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_scoped_run_cancellation_context.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_sdk_metrics_contract.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_serializer.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_signals.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_sleep.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_sticky_execution.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_storage_admission.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_sync.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_sync_activities.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_telemetry.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_telemetry_carrier.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_testing_failures.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_testing_harness.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_testing_nexus.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_threaded_heartbeat_example.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_tls_trust.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_transport_diagnostics.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_update_signal_condition_replay.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_updates.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_wait_condition.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_worker.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_worker_deregistration.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_worker_observation.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_worker_session.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_worker_status.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_worker_telemetry_wire.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_entry_arguments.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_entrypoint_validation.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_handler_validation.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_identity.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_result_timeout.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_streams.py +0 -0
- {durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_updates_conformance.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "durable-workflow"
|
|
7
|
-
version = "2.13.
|
|
7
|
+
version = "2.13.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"
|
|
@@ -79,8 +79,8 @@ 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.13.
|
|
83
|
-
registry-version = "2.13.
|
|
82
|
+
product-train = "2.13.1"
|
|
83
|
+
registry-version = "2.13.1"
|
|
84
84
|
supported-server-versions = "2.8.0"
|
|
85
85
|
supported-server-release-baselines = { "2" = "2.8.0", "3" = "3.0.0" }
|
|
86
86
|
worker-protocol-version = "1.20"
|
|
@@ -2205,9 +2205,15 @@ class WorkflowContext:
|
|
|
2205
2205
|
return self._cancellation_context
|
|
2206
2206
|
|
|
2207
2207
|
def _observe_cancellation_replay_time(self, event: Mapping[str, Any] | None) -> None:
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2208
|
+
payload = event.get("payload") if event is not None else None
|
|
2209
|
+
# A step reused by redrive keeps the time it was first recorded, as in
|
|
2210
|
+
# Core's RedriveReplayClock::recordedAt; it overrides the event's own time.
|
|
2211
|
+
if isinstance(payload, Mapping) and isinstance(payload.get("reused_recorded_at"), str):
|
|
2212
|
+
timestamp: Any = payload["reused_recorded_at"]
|
|
2213
|
+
else:
|
|
2214
|
+
timestamp = event.get("timestamp") if event is not None else None
|
|
2215
|
+
if timestamp is None and event is not None:
|
|
2216
|
+
timestamp = event.get("recorded_at")
|
|
2211
2217
|
self._cancellation_replay_time_available = False
|
|
2212
2218
|
if not isinstance(timestamp, str):
|
|
2213
2219
|
return
|
|
@@ -4680,6 +4686,10 @@ def _replay_state(
|
|
|
4680
4686
|
explicit_time = "timestamp" in start_payload
|
|
4681
4687
|
ts = start_payload.get("timestamp") if explicit_time else ev.get("recorded_at")
|
|
4682
4688
|
clock_source = "payload.timestamp" if explicit_time else "recorded_at"
|
|
4689
|
+
# A redriven run replays from the failed run's start time, as Core does.
|
|
4690
|
+
if start_payload.get("recovery_kind") == "redrive" and "replayed_started_at" in start_payload:
|
|
4691
|
+
ts = start_payload.get("replayed_started_at")
|
|
4692
|
+
clock_source = "payload.replayed_started_at"
|
|
4683
4693
|
replay_clock_error = f"Workflow replay clock unavailable: invalid or missing WorkflowStarted.{clock_source}"
|
|
4684
4694
|
if isinstance(ts, str):
|
|
4685
4695
|
with contextlib.suppress(ValueError):
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_remaining_time.py
RENAMED
|
@@ -263,3 +263,22 @@ def test_inline_local_result_persistence_preserves_the_first_execution_budget()
|
|
|
263
263
|
]
|
|
264
264
|
assert observations == [22.123456, 22.123456]
|
|
265
265
|
assert calls == ["once"]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def test_a_reused_step_keeps_its_original_time_for_the_remaining_budget() -> None:
|
|
269
|
+
class Cleanup:
|
|
270
|
+
def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
|
|
271
|
+
try:
|
|
272
|
+
yield ctx.start_timer(60)
|
|
273
|
+
except WorkflowCancelled as error:
|
|
274
|
+
assert error.context is not None
|
|
275
|
+
with ctx.cancellation_shield():
|
|
276
|
+
yield ctx.schedule_activity("cleanup", [])
|
|
277
|
+
return error.context.remaining()
|
|
278
|
+
|
|
279
|
+
reused = completed_activity(2, "cleanup", "cleaned")
|
|
280
|
+
reused["payload"] = {**reused["payload"], "reused_from_run_id": "failed-run",
|
|
281
|
+
"reused_recorded_at": "2026-10-01T00:00:12Z"}
|
|
282
|
+
events = history() + [{**reused, "timestamp": "2026-10-01T00:00:29Z"}]
|
|
283
|
+
# Redrive recorded the step again at 00:29; its workflow time stays 00:12.
|
|
284
|
+
assert replay(Cleanup, events, [], run_id="run-1").commands == [CompleteWorkflow(18.123456)]
|
|
@@ -674,6 +674,22 @@ DOCUMENTED_HISTORY_EVENT_PAYLOAD_KEYS: dict[str, tuple[str, ...]] = {
|
|
|
674
674
|
}
|
|
675
675
|
|
|
676
676
|
|
|
677
|
+
# Keys Core adds only to runs created by redrive (workflow#864). The SDK reads
|
|
678
|
+
# replayed_started_at for the start clock and reused_recorded_at for step time.
|
|
679
|
+
REDRIVE_HISTORY_EVENT_PAYLOAD_KEYS = {
|
|
680
|
+
"WorkflowStarted": ("resume_step_sequence", "recovery_kind", "replayed_started_at"),
|
|
681
|
+
"ActivityCompleted": ("reused_from_run_id", "reused_activity_execution_id", "reused_recorded_at"),
|
|
682
|
+
"TimerFired": ("reused_from_run_id", "reused_recorded_at"),
|
|
683
|
+
"SideEffectRecorded": ("reused_from_run_id", "reused_recorded_at"),
|
|
684
|
+
"VersionMarkerRecorded": ("reused_from_run_id", "reused_recorded_at"),
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
for _event_type, _keys in REDRIVE_HISTORY_EVENT_PAYLOAD_KEYS.items():
|
|
688
|
+
DOCUMENTED_HISTORY_EVENT_PAYLOAD_KEYS[_event_type] = (
|
|
689
|
+
*DOCUMENTED_HISTORY_EVENT_PAYLOAD_KEYS[_event_type],
|
|
690
|
+
*(key for key in _keys if key not in DOCUMENTED_HISTORY_EVENT_PAYLOAD_KEYS[_event_type]),
|
|
691
|
+
)
|
|
692
|
+
|
|
677
693
|
SDK_INTERPRETED_HISTORY_EVENTS = frozenset(
|
|
678
694
|
{
|
|
679
695
|
"WorkflowStarted",
|
|
@@ -732,9 +748,18 @@ def test_schedule_history_events_are_preserved_only() -> None:
|
|
|
732
748
|
|
|
733
749
|
def test_php_history_fixture_key_expectations_match_documented_contract() -> None:
|
|
734
750
|
for event_type, fixture_keys in PHP_HISTORY_EVENT_EXPECTED_KEYS.items():
|
|
735
|
-
|
|
751
|
+
# Published PHP fixtures come from ordinary runs, which never carry redrive keys.
|
|
752
|
+
documented_keys = set(DOCUMENTED_HISTORY_EVENT_PAYLOAD_KEYS[event_type]) - set(
|
|
753
|
+
REDRIVE_HISTORY_EVENT_PAYLOAD_KEYS.get(event_type, ())
|
|
754
|
+
)
|
|
736
755
|
expected_keys = set(fixture_keys)
|
|
737
756
|
tolerated_extra_keys = set(SDK_TOLERATED_FIXTURE_EXTRA_KEYS.get(event_type, frozenset()))
|
|
738
757
|
|
|
739
758
|
assert expected_keys - documented_keys == tolerated_extra_keys, event_type
|
|
740
759
|
assert documented_keys - expected_keys == set(), event_type
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def test_redrive_payload_keys_are_documented_on_the_interpreted_events() -> None:
|
|
763
|
+
for event_type, keys in REDRIVE_HISTORY_EVENT_PAYLOAD_KEYS.items():
|
|
764
|
+
assert event_type in SDK_INTERPRETED_HISTORY_EVENTS, event_type
|
|
765
|
+
assert set(keys) <= set(DOCUMENTED_HISTORY_EVENT_PAYLOAD_KEYS[event_type]), event_type
|
|
@@ -7,7 +7,7 @@ from uuid import UUID
|
|
|
7
7
|
|
|
8
8
|
import pytest
|
|
9
9
|
|
|
10
|
-
from durable_workflow import workflow
|
|
10
|
+
from durable_workflow import serializer, workflow
|
|
11
11
|
from durable_workflow.errors import NonDeterministicReplayError
|
|
12
12
|
from durable_workflow.workflow import CompleteWorkflow, WorkflowContext, replay
|
|
13
13
|
|
|
@@ -91,3 +91,43 @@ def test_missing_replay_time_is_explicit(history: list[dict[str, Any]]) -> None:
|
|
|
91
91
|
def test_empty_local_authoring_remains_available() -> None:
|
|
92
92
|
assert datetime.fromisoformat(result(PersistedClockWorkflow, [])).tzinfo is not None
|
|
93
93
|
assert UUID(result(PersistedUUID7Workflow, [])).version == 7
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@workflow.defn(name="tests.replay.redrive-clock")
|
|
97
|
+
class RedriveClockWorkflow:
|
|
98
|
+
def run(self, ctx: WorkflowContext): # type: ignore[no-untyped-def]
|
|
99
|
+
before = ctx.now().isoformat(timespec="microseconds")
|
|
100
|
+
token = str(ctx.uuid7())
|
|
101
|
+
recorded = yield ctx.schedule_activity("tests.first", [before])
|
|
102
|
+
return [before, token, recorded]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def redriven_history(**start_payload: Any) -> list[dict[str, Any]]:
|
|
106
|
+
return [
|
|
107
|
+
{"event_type": "WorkflowStarted", "recorded_at": "2026-06-01T10:05:00.000000Z", "payload": start_payload},
|
|
108
|
+
{"event_type": "ActivityCompleted", "recorded_at": "2026-06-01T10:05:00.500000Z",
|
|
109
|
+
"payload": {"sequence": 1, "activity_type": "tests.first",
|
|
110
|
+
"result": serializer.encode("recorded", codec="avro"), "payload_codec": "avro",
|
|
111
|
+
"reused_from_run_id": "failed-run",
|
|
112
|
+
"reused_recorded_at": "2026-06-01T10:00:01.000000Z"}},
|
|
113
|
+
]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_redriven_run_replays_from_the_failed_run_start_time() -> None:
|
|
117
|
+
history = redriven_history(recovery_kind="redrive", replayed_started_at="2026-06-01T10:00:00.000000Z")
|
|
118
|
+
before, token, recorded = result(RedriveClockWorkflow, history)
|
|
119
|
+
assert before == "2026-06-01T10:00:00.000000+00:00"
|
|
120
|
+
assert UUID(token).int >> 80 == int(datetime.fromisoformat(before).timestamp() * 1000)
|
|
121
|
+
assert recorded == "recorded"
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_replayed_start_time_requires_a_redrive_start() -> None:
|
|
125
|
+
history = redriven_history(replayed_started_at="2026-06-01T10:00:00.000000Z")
|
|
126
|
+
assert result(RedriveClockWorkflow, history)[0] == "2026-06-01T10:05:00.000000+00:00"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.mark.parametrize("invalid", [None, 123, "", "not-a-time", "2026-06-01T10:00:00"])
|
|
130
|
+
def test_invalid_replayed_start_time_is_refused_without_fallback(invalid: Any) -> None:
|
|
131
|
+
history = redriven_history(recovery_kind="redrive", replayed_started_at=invalid)
|
|
132
|
+
with pytest.raises(NonDeterministicReplayError, match="replayed_started_at"):
|
|
133
|
+
replay(RedriveClockWorkflow, history, [], run_id="redriven-run", payload_codec="avro")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_activity_process.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_cancellation_scope.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_failure_reporting.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_prepared_local_activity.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_query_task_authority.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/_sticky_workflow_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/auth_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/external_storage.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/history_bundle_verify.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/python_conformance.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/replay_conformance.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/server_compatibility.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow/telemetry_carrier.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/src/durable_workflow.egg-info/top_level.txt
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_cancellation_policies.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_activity_heartbeat_authority.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_scope_admission.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_scope_authoring.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_scope_boundary.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cancellation_scope_opening.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_control_plane_parity_fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cooperative_cancellation_client.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_cooperative_cancellation_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_docstring_cross_references.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_parallel_completion_order_replay.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_prepared_local_activity_client.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_prepared_local_activity_groups.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_prepared_local_activity_policies.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_prepared_local_activity_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_run_cancellation_scope_replay.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_runtime_external_payload_transport.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_scoped_run_cancellation_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_standalone_activity_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_threaded_heartbeat_example.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_update_signal_condition_replay.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_entrypoint_validation.py
RENAMED
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_handler_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.13.0 → durable_workflow-2.13.1}/tests/test_workflow_updates_conformance.py
RENAMED
|
File without changes
|