durable-workflow 2.5.10__tar.gz → 2.5.11__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.5.10/src/durable_workflow.egg-info → durable_workflow-2.5.11}/PKG-INFO +9 -1
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/README.md +8 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/pyproject.toml +3 -3
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/replay_conformance.py +35 -16
- {durable_workflow-2.5.10 → durable_workflow-2.5.11/src/durable_workflow.egg-info}/PKG-INFO +9 -1
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_replay_conformance.py +44 -3
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_replay_regression_corpus.py +26 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/LICENSE +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/setup.cfg +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_activity_process.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_cancellation_scope.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_cancellation_scope_history.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_cooperative_cancellation.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_prepared_local_activity.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_sticky_workflow_cache.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/cancellation.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/client.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/nexus.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/python_conformance.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/worker.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/worker_session.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/workflow.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/workflow_updates_conformance.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_activity_cancellation_policies.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_activity_context.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_activity_process.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_auth_composition.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_avro_value_benchmark.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_avro_value_protocol.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_context.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_remaining_time.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_scope_admission.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_scope_authoring.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_scope_boundary.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_scope_opening.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_child_workflow_policies.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_ci_checkout.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_ci_integration_endpoint.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_client.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_client_timeouts.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_codec_regression_corpus.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_committed_cancellation_scope_descendants.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_committed_cancellation_scope_groups.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_committed_cancellation_scope_history.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_committed_cancellation_scope_replay.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_condition_replay_drift.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cooperative_cancellation.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cooperative_cancellation_client.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cooperative_cancellation_worker.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cooperative_remote_worker.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_docstring_cross_references.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_durable_selection.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_errors.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_external_storage.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_external_task_input.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_external_task_result.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_invocable.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_message_streams.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_metrics.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_nexus_namespace.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_nexus_wire.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_parallel_completion_order_replay.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_parallel_saga_authoring.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_patch_history.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_pending_operation_replay.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_prepared_local_activity_client.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_prepared_local_activity_groups.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_prepared_local_activity_policies.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_prepared_local_activity_worker.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_pypi_project_surface.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_python_conformance.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_queries.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_redrive_replay.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_registration_ambiguity.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_release_metadata.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_replay.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_replay_verify.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_retry_policy.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_run_cancellation_scope_replay.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_runtime_external_payload_transport.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_schedules.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_scoped_run_cancellation_context.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_serializer.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_signals.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_sleep.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_sticky_execution.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_storage_admission.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_sync.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_testing_harness.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_update_signal_condition_replay.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_updates.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_wait_condition.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_worker.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_worker_session.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_workflow_entrypoint_validation.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_workflow_result_timeout.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_workflow_streams.py +0 -0
- {durable_workflow-2.5.10 → durable_workflow-2.5.11}/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.5.
|
|
3
|
+
Version: 2.5.11
|
|
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
|
|
@@ -175,6 +175,14 @@ for immutable context, operation policies, shielded cleanup and recovery.
|
|
|
175
175
|
|
|
176
176
|
## Development
|
|
177
177
|
|
|
178
|
+
The installed `durable-workflow-replay-conformance` command runs an offline
|
|
179
|
+
Python replay shard. Its `sdk-python` artifact pin must match the installed
|
|
180
|
+
distribution version reported by `importlib.metadata.version("durable-workflow")`.
|
|
181
|
+
The report records that installed identity separately from the declared platform
|
|
182
|
+
tuple and fails if metadata is missing or the SDK pin differs. Replay success
|
|
183
|
+
does not prove installation or execution of the other declared artifacts; the
|
|
184
|
+
aggregate runner supplies its own verified installation receipts.
|
|
185
|
+
|
|
178
186
|
```bash
|
|
179
187
|
pip install -e '.[dev]'
|
|
180
188
|
ruff check src/ tests/
|
|
@@ -135,6 +135,14 @@ for immutable context, operation policies, shielded cleanup and recovery.
|
|
|
135
135
|
|
|
136
136
|
## Development
|
|
137
137
|
|
|
138
|
+
The installed `durable-workflow-replay-conformance` command runs an offline
|
|
139
|
+
Python replay shard. Its `sdk-python` artifact pin must match the installed
|
|
140
|
+
distribution version reported by `importlib.metadata.version("durable-workflow")`.
|
|
141
|
+
The report records that installed identity separately from the declared platform
|
|
142
|
+
tuple and fails if metadata is missing or the SDK pin differs. Replay success
|
|
143
|
+
does not prove installation or execution of the other declared artifacts; the
|
|
144
|
+
aggregate runner supplies its own verified installation receipts.
|
|
145
|
+
|
|
138
146
|
```bash
|
|
139
147
|
pip install -e '.[dev]'
|
|
140
148
|
ruff check src/ tests/
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "durable-workflow"
|
|
7
|
-
version = "2.5.
|
|
7
|
+
version = "2.5.11"
|
|
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"
|
|
@@ -71,8 +71,8 @@ durable-workflow-replay-conformance = "durable_workflow.replay_conformance:main"
|
|
|
71
71
|
durable-workflow-workflow-updates-conformance = "durable_workflow.workflow_updates_conformance:main"
|
|
72
72
|
|
|
73
73
|
[tool.durable-workflow]
|
|
74
|
-
product-train = "2.5.
|
|
75
|
-
registry-version = "2.5.
|
|
74
|
+
product-train = "2.5.11"
|
|
75
|
+
registry-version = "2.5.11"
|
|
76
76
|
supported-server-versions = "2.5.16"
|
|
77
77
|
worker-protocol-version = "1.20"
|
|
78
78
|
control-plane-version = "2"
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/replay_conformance.py
RENAMED
|
@@ -15,6 +15,7 @@ import json
|
|
|
15
15
|
import sys
|
|
16
16
|
from collections.abc import Mapping, Sequence
|
|
17
17
|
from datetime import datetime, timezone
|
|
18
|
+
from importlib import metadata
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
from typing import Any
|
|
20
21
|
|
|
@@ -204,6 +205,17 @@ def compose_report(
|
|
|
204
205
|
started_at = _timestamp()
|
|
205
206
|
versions = _canonical_artifact_metadata(dict(artifact_versions or {}))
|
|
206
207
|
sources = _canonical_artifact_metadata(dict(artifact_sources or {}))
|
|
208
|
+
try:
|
|
209
|
+
installed_version = metadata.version("durable-workflow")
|
|
210
|
+
except metadata.PackageNotFoundError:
|
|
211
|
+
installed_version = None
|
|
212
|
+
identity = {
|
|
213
|
+
"runtime": "sdk-python",
|
|
214
|
+
"package": "durable-workflow",
|
|
215
|
+
"version": installed_version,
|
|
216
|
+
"version_source": "installed_distribution_metadata" if installed_version else "unavailable",
|
|
217
|
+
"declared_version": _artifact_metadata(versions, "sdk-python"),
|
|
218
|
+
}
|
|
207
219
|
scenario_results: dict[str, dict[str, Any]] = {}
|
|
208
220
|
findings: list[dict[str, Any]] = []
|
|
209
221
|
finding_links: dict[str, list[dict[str, Any]]] = {}
|
|
@@ -212,7 +224,7 @@ def compose_report(
|
|
|
212
224
|
scenario_results,
|
|
213
225
|
findings,
|
|
214
226
|
finding_links,
|
|
215
|
-
_published_artifact_scenario(versions, sources),
|
|
227
|
+
_published_artifact_scenario(versions, sources, installed_version),
|
|
216
228
|
)
|
|
217
229
|
|
|
218
230
|
for family, scenario_id in COMPLETED_SCENARIOS.items():
|
|
@@ -242,6 +254,9 @@ def compose_report(
|
|
|
242
254
|
has_failures = any(
|
|
243
255
|
scenario.get("status") != "pass" for scenario in scenario_results.values()
|
|
244
256
|
)
|
|
257
|
+
for scenario in scenario_results.values():
|
|
258
|
+
scenario["implementation_identity"] = dict(identity)
|
|
259
|
+
scenario["artifact_tuple_evidence"] = "declared"
|
|
245
260
|
|
|
246
261
|
return {
|
|
247
262
|
"schema": RESULT_SCHEMA,
|
|
@@ -252,6 +267,8 @@ def compose_report(
|
|
|
252
267
|
"finished_at": _timestamp(),
|
|
253
268
|
"artifact_versions": versions,
|
|
254
269
|
"artifact_sources": sources,
|
|
270
|
+
"artifact_tuple_evidence": "declared",
|
|
271
|
+
"implementation_identity": identity,
|
|
255
272
|
"runtime_matrix": {
|
|
256
273
|
"runtimes": ["sdk-python"],
|
|
257
274
|
},
|
|
@@ -307,6 +324,7 @@ def _append_scenario(
|
|
|
307
324
|
def _published_artifact_scenario(
|
|
308
325
|
artifact_versions: Mapping[str, str],
|
|
309
326
|
artifact_sources: Mapping[str, str],
|
|
327
|
+
installed_version: str | None,
|
|
310
328
|
) -> dict[str, Any]:
|
|
311
329
|
missing_versions: list[str] = []
|
|
312
330
|
missing_sources: list[str] = []
|
|
@@ -338,14 +356,14 @@ def _published_artifact_scenario(
|
|
|
338
356
|
untrusted_sources[artifact] = source
|
|
339
357
|
|
|
340
358
|
missing = sorted(set(missing_versions + missing_sources))
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
359
|
+
declarations_valid = not missing and not rejected_versions and not forbidden_sources and not untrusted_sources
|
|
360
|
+
declared_version = _artifact_metadata(artifact_versions, "sdk-python")
|
|
361
|
+
identity_error = None
|
|
362
|
+
if not installed_version:
|
|
363
|
+
identity_error = "installed_sdk_metadata_missing"
|
|
364
|
+
elif declared_version != installed_version:
|
|
365
|
+
identity_error = "installed_sdk_version_mismatch"
|
|
366
|
+
status = "pass" if declarations_valid and identity_error is None else "fail"
|
|
349
367
|
|
|
350
368
|
return _scenario(
|
|
351
369
|
"published_artifact_install_only",
|
|
@@ -361,8 +379,14 @@ def _published_artifact_scenario(
|
|
|
361
379
|
"rejected_versions": rejected_versions,
|
|
362
380
|
"forbidden_sources": forbidden_sources,
|
|
363
381
|
"untrusted_sources": untrusted_sources,
|
|
364
|
-
"
|
|
365
|
-
"
|
|
382
|
+
"declared_artifacts_published_only": bool(declarations_valid),
|
|
383
|
+
"declared_sdk_version": declared_version,
|
|
384
|
+
"installed_sdk_version": installed_version,
|
|
385
|
+
"sdk_identity_error": identity_error,
|
|
386
|
+
"sdk_version_matches": bool(installed_version and declared_version == installed_version),
|
|
387
|
+
"artifact_tuple_evidence": "declared",
|
|
388
|
+
"published_artifacts_only": False,
|
|
389
|
+
"published_install_tuple_proven": False,
|
|
366
390
|
},
|
|
367
391
|
)
|
|
368
392
|
|
|
@@ -589,11 +613,6 @@ def _scenario(
|
|
|
589
613
|
"scenario_id": scenario_id,
|
|
590
614
|
"status": status,
|
|
591
615
|
"published_artifact_versions": dict(artifact_versions),
|
|
592
|
-
"implementation_identity": {
|
|
593
|
-
"runtime": "sdk-python",
|
|
594
|
-
"package": "durable-workflow",
|
|
595
|
-
"version": _artifact_metadata(artifact_versions, "sdk-python"),
|
|
596
|
-
},
|
|
597
616
|
"runtime_matrix": {
|
|
598
617
|
"runtimes": ["sdk-python"],
|
|
599
618
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: durable-workflow
|
|
3
|
-
Version: 2.5.
|
|
3
|
+
Version: 2.5.11
|
|
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
|
|
@@ -175,6 +175,14 @@ for immutable context, operation policies, shielded cleanup and recovery.
|
|
|
175
175
|
|
|
176
176
|
## Development
|
|
177
177
|
|
|
178
|
+
The installed `durable-workflow-replay-conformance` command runs an offline
|
|
179
|
+
Python replay shard. Its `sdk-python` artifact pin must match the installed
|
|
180
|
+
distribution version reported by `importlib.metadata.version("durable-workflow")`.
|
|
181
|
+
The report records that installed identity separately from the declared platform
|
|
182
|
+
tuple and fails if metadata is missing or the SDK pin differs. Replay success
|
|
183
|
+
does not prove installation or execution of the other declared artifacts; the
|
|
184
|
+
aggregate runner supplies its own verified installation receipts.
|
|
185
|
+
|
|
178
186
|
```bash
|
|
179
187
|
pip install -e '.[dev]'
|
|
180
188
|
ruff check src/ tests/
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
from importlib import metadata
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
from typing import Any
|
|
6
7
|
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
7
10
|
from durable_workflow.replay_conformance import (
|
|
8
11
|
COVERAGE_SCOPE,
|
|
9
12
|
RESULT_SCHEMA,
|
|
@@ -25,6 +28,11 @@ def artifact_versions() -> dict[str, str]:
|
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
|
|
31
|
+
@pytest.fixture(autouse=True)
|
|
32
|
+
def installed_sdk_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
33
|
+
monkeypatch.setattr(metadata, "version", lambda package: "0.4.73")
|
|
34
|
+
|
|
35
|
+
|
|
28
36
|
def artifact_sources() -> dict[str, str]:
|
|
29
37
|
return {
|
|
30
38
|
"server": "docker_image",
|
|
@@ -81,7 +89,7 @@ def test_compose_report_emits_python_replay_conformance_shard() -> None:
|
|
|
81
89
|
assert indexed[scenario_id]["status"] == "pass"
|
|
82
90
|
|
|
83
91
|
|
|
84
|
-
def
|
|
92
|
+
def test_report_validates_declared_tuple_without_claiming_installation_proof() -> None:
|
|
85
93
|
report = compose_report(
|
|
86
94
|
artifact_versions=artifact_versions(),
|
|
87
95
|
artifact_sources=artifact_sources(),
|
|
@@ -90,13 +98,46 @@ def test_report_proves_published_artifact_install_policy() -> None:
|
|
|
90
98
|
observed = published["observed_outputs"]
|
|
91
99
|
|
|
92
100
|
assert published["status"] == "pass"
|
|
93
|
-
assert observed["published_artifacts_only"] is
|
|
94
|
-
assert observed["published_install_tuple_proven"] is
|
|
101
|
+
assert observed["published_artifacts_only"] is False
|
|
102
|
+
assert observed["published_install_tuple_proven"] is False
|
|
103
|
+
assert observed["declared_artifacts_published_only"] is True
|
|
104
|
+
assert observed["artifact_tuple_evidence"] == "declared"
|
|
105
|
+
assert observed["installed_sdk_version"] == "0.4.73"
|
|
106
|
+
assert observed["sdk_version_matches"] is True
|
|
107
|
+
assert observed["sdk_identity_error"] is None
|
|
108
|
+
assert report["implementation_identity"]["version"] == "0.4.73"
|
|
95
109
|
assert observed["missing_artifacts"] == []
|
|
96
110
|
assert observed["artifact_sources"]["server"] == "docker_image"
|
|
97
111
|
assert observed["artifact_sources"]["sdk-python"] == "pypi_package"
|
|
98
112
|
|
|
99
113
|
|
|
114
|
+
def test_report_rejects_declared_sdk_version_mismatch(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
115
|
+
monkeypatch.setattr(metadata, "version", lambda package: "2.5.10")
|
|
116
|
+
report = compose_report(artifact_versions=artifact_versions(), artifact_sources=artifact_sources())
|
|
117
|
+
install = scenarios(report)["published_artifact_install_only"]
|
|
118
|
+
assert report["outcome"] == "fail"
|
|
119
|
+
assert install["status"] == "fail"
|
|
120
|
+
assert install["observed_outputs"]["installed_sdk_version"] == "2.5.10"
|
|
121
|
+
assert install["observed_outputs"]["declared_sdk_version"] == "0.4.73"
|
|
122
|
+
assert install["observed_outputs"]["sdk_identity_error"] == "installed_sdk_version_mismatch"
|
|
123
|
+
assert install["observed_outputs"]["published_install_tuple_proven"] is False
|
|
124
|
+
assert all(scenario["implementation_identity"]["version"] == "2.5.10" for scenario in report["scenario_results"])
|
|
125
|
+
assert report["finding_links"]["published_artifact_install_only"]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def test_report_rejects_missing_installed_sdk_metadata(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
129
|
+
def missing_metadata(package: str) -> str:
|
|
130
|
+
raise metadata.PackageNotFoundError(package)
|
|
131
|
+
|
|
132
|
+
monkeypatch.setattr(metadata, "version", missing_metadata)
|
|
133
|
+
report = compose_report(artifact_versions=artifact_versions(), artifact_sources=artifact_sources())
|
|
134
|
+
install = scenarios(report)["published_artifact_install_only"]
|
|
135
|
+
assert report["outcome"] == "fail"
|
|
136
|
+
assert install["observed_outputs"]["sdk_identity_error"] == "installed_sdk_metadata_missing"
|
|
137
|
+
assert install["implementation_identity"]["version"] is None
|
|
138
|
+
assert install["observed_outputs"]["published_install_tuple_proven"] is False
|
|
139
|
+
|
|
140
|
+
|
|
100
141
|
def test_report_rejects_missing_versions_and_sources() -> None:
|
|
101
142
|
report = compose_report(
|
|
102
143
|
artifact_versions={"sdk-python": "0.4.73"},
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from collections.abc import Mapping, Sequence
|
|
5
|
+
from importlib import metadata
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
from unittest.mock import AsyncMock
|
|
@@ -11,6 +12,7 @@ import pytest
|
|
|
11
12
|
from durable_workflow import Replayer, Worker, serializer, workflow
|
|
12
13
|
from durable_workflow.client import PROTOCOL_VERSION, Client, WorkflowExecution, WorkflowStreamAppendItem
|
|
13
14
|
from durable_workflow.errors import NonDeterministicReplayError, WorkflowCancelled, WorkflowPayloadDecodeError
|
|
15
|
+
from durable_workflow.replay_conformance import compose_report
|
|
14
16
|
from durable_workflow.workflow import (
|
|
15
17
|
LocalActivityExecutionAborted,
|
|
16
18
|
WorkflowContext,
|
|
@@ -371,6 +373,27 @@ def _execute_fixture(fixture: dict[str, Any]) -> list[dict[str, Any]]:
|
|
|
371
373
|
assert isinstance(workflow, dict)
|
|
372
374
|
workflow_type = workflow.get("type")
|
|
373
375
|
assert isinstance(workflow_type, str) and workflow_type
|
|
376
|
+
if workflow_type == "sdk.replay-conformance-report":
|
|
377
|
+
# This public command executes the SDK's offline replay scenarios. Its
|
|
378
|
+
# regression input is artifact declarations, not an invented history.
|
|
379
|
+
assert "history" not in fixture
|
|
380
|
+
inputs = workflow.get("input")
|
|
381
|
+
assert isinstance(inputs, list) and len(inputs) == 1 and isinstance(inputs[0], dict)
|
|
382
|
+
report = compose_report(**inputs[0])
|
|
383
|
+
install = next(scenario for scenario in report["scenario_results"]
|
|
384
|
+
if scenario["scenario_id"] == "published_artifact_install_only")
|
|
385
|
+
observed = install["observed_outputs"]
|
|
386
|
+
commands = [{
|
|
387
|
+
"type": "replay_conformance_report",
|
|
388
|
+
"outcome": report["outcome"],
|
|
389
|
+
"sdk_identity_error": observed.get("sdk_identity_error"),
|
|
390
|
+
"declared_sdk_version": observed.get("declared_sdk_version"),
|
|
391
|
+
"published_install_tuple_proven": observed["published_install_tuple_proven"],
|
|
392
|
+
}]
|
|
393
|
+
_assert_matches(fixture["command_sequence"], commands, f"{fixture['id']}.command_sequence")
|
|
394
|
+
_assert_matches(fixture["expected"], {"command_sequence": commands}, f"{fixture['id']}.expected")
|
|
395
|
+
assert report["implementation_identity"]["version"] == metadata.version("durable-workflow")
|
|
396
|
+
return commands
|
|
374
397
|
assert workflow_type in WORKFLOW_TYPES, (
|
|
375
398
|
f"replay fixture workflow {workflow_type!r} has no Python implementation; "
|
|
376
399
|
"register its reproducer workflow in WORKFLOWS"
|
|
@@ -462,6 +485,9 @@ def test_checked_in_replay_regression_corpus_uses_official_replayer(
|
|
|
462
485
|
assert isinstance(workflow, dict)
|
|
463
486
|
expected = fixture.get("expected")
|
|
464
487
|
assert isinstance(expected, dict) and expected
|
|
488
|
+
if workflow.get("type") == "sdk.replay-conformance-report":
|
|
489
|
+
_execute_fixture(fixture)
|
|
490
|
+
return
|
|
465
491
|
expected_error = expected.get("error")
|
|
466
492
|
expected_replay_error = fixture.get("expected_replay_error")
|
|
467
493
|
if isinstance(expected_replay_error, dict):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_activity_process.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_cancellation_scope.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_prepared_local_activity.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/_sticky_workflow_cache.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/auth_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/external_storage.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/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.5.10 → durable_workflow-2.5.11}/src/durable_workflow/python_conformance.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.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/src/durable_workflow.egg-info/top_level.txt
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_activity_cancellation_policies.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_remaining_time.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_scope_admission.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_scope_authoring.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cancellation_scope_boundary.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/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
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_control_plane_parity_fixtures.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cooperative_cancellation_client.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_cooperative_cancellation_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/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
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_parallel_completion_order_replay.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_prepared_local_activity_client.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_prepared_local_activity_groups.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_prepared_local_activity_policies.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/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
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_run_cancellation_scope_replay.py
RENAMED
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_runtime_external_payload_transport.py
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_scoped_run_cancellation_context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_standalone_activity_client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_update_signal_condition_replay.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_workflow_entrypoint_validation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-2.5.10 → durable_workflow-2.5.11}/tests/test_workflow_updates_conformance.py
RENAMED
|
File without changes
|