durable-workflow 2.6.0rc3__tar.gz → 2.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. {durable_workflow-2.6.0rc3/src/durable_workflow.egg-info → durable_workflow-2.6.1}/PKG-INFO +2 -1
  2. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/README.md +1 -0
  3. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/pyproject.toml +3 -3
  4. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/client.py +12 -3
  5. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/errors.py +5 -3
  6. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1/src/durable_workflow.egg-info}/PKG-INFO +2 -1
  7. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_errors.py +5 -3
  8. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/LICENSE +0 -0
  9. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/setup.cfg +0 -0
  10. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/__init__.py +0 -0
  11. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/_activity_process.py +0 -0
  12. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/_avro.py +0 -0
  13. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/_cancellation_scope.py +0 -0
  14. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/_cancellation_scope_history.py +0 -0
  15. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/_cooperative_cancellation.py +0 -0
  16. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/_prepared_local_activity.py +0 -0
  17. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/_query_task_authority.py +0 -0
  18. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/_sticky_workflow_cache.py +0 -0
  19. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/activity.py +0 -0
  20. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/auth_composition.py +0 -0
  21. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/cancellation.py +0 -0
  22. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/external_storage.py +0 -0
  23. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/external_task_input.py +0 -0
  24. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/external_task_result.py +0 -0
  25. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/history_bundle_verify.py +0 -0
  26. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/interceptors.py +0 -0
  27. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/invocable.py +0 -0
  28. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/metrics.py +0 -0
  29. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/nexus.py +0 -0
  30. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/py.typed +0 -0
  31. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/python_conformance.py +0 -0
  32. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/replay_conformance.py +0 -0
  33. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/replay_verify.py +0 -0
  34. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/retry_policy.py +0 -0
  35. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/serializer.py +0 -0
  36. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/sync.py +0 -0
  37. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/testing.py +0 -0
  38. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/worker.py +0 -0
  39. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/worker_session.py +0 -0
  40. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/workflow.py +0 -0
  41. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow/workflow_updates_conformance.py +0 -0
  42. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
  43. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
  44. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow.egg-info/entry_points.txt +0 -0
  45. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow.egg-info/requires.txt +0 -0
  46. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/src/durable_workflow.egg-info/top_level.txt +0 -0
  47. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_activity_cancellation_policies.py +0 -0
  48. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_activity_context.py +0 -0
  49. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_activity_process.py +0 -0
  50. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_auth_composition.py +0 -0
  51. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_avro_value_benchmark.py +0 -0
  52. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_avro_value_protocol.py +0 -0
  53. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cancellation_context.py +0 -0
  54. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cancellation_remaining_time.py +0 -0
  55. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cancellation_scope_admission.py +0 -0
  56. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cancellation_scope_authoring.py +0 -0
  57. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cancellation_scope_boundary.py +0 -0
  58. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cancellation_scope_opening.py +0 -0
  59. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_child_workflow_policies.py +0 -0
  60. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_ci_checkout.py +0 -0
  61. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_ci_integration_endpoint.py +0 -0
  62. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_client.py +0 -0
  63. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_client_timeouts.py +0 -0
  64. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_codec_regression_corpus.py +0 -0
  65. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_committed_cancellation_scope_descendants.py +0 -0
  66. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_committed_cancellation_scope_groups.py +0 -0
  67. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_committed_cancellation_scope_history.py +0 -0
  68. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_committed_cancellation_scope_replay.py +0 -0
  69. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_condition_replay_drift.py +0 -0
  70. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_control_plane_parity_fixtures.py +0 -0
  71. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cooperative_cancellation.py +0 -0
  72. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cooperative_cancellation_client.py +0 -0
  73. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cooperative_cancellation_worker.py +0 -0
  74. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_cooperative_remote_worker.py +0 -0
  75. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_docstring_cross_references.py +0 -0
  76. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_durable_selection.py +0 -0
  77. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_external_storage.py +0 -0
  78. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_external_task_input.py +0 -0
  79. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_external_task_result.py +0 -0
  80. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_golden_history_replay.py +0 -0
  81. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_history_bundle_verify.py +0 -0
  82. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_history_event_contract.py +0 -0
  83. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_invocable.py +0 -0
  84. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_message_streams.py +0 -0
  85. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_metrics.py +0 -0
  86. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_nexus_durable_wait.py +0 -0
  87. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_nexus_namespace.py +0 -0
  88. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_nexus_wire.py +0 -0
  89. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_order_processing_example.py +0 -0
  90. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_parallel_completion_order_replay.py +0 -0
  91. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_parallel_saga_authoring.py +0 -0
  92. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_patch_history.py +0 -0
  93. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_pending_operation_replay.py +0 -0
  94. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_prepared_local_activity_client.py +0 -0
  95. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_prepared_local_activity_groups.py +0 -0
  96. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_prepared_local_activity_policies.py +0 -0
  97. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_prepared_local_activity_worker.py +0 -0
  98. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_public_boundary_scanner.py +0 -0
  99. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_pypi_project_surface.py +0 -0
  100. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_python_conformance.py +0 -0
  101. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_queries.py +0 -0
  102. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_query_task_authority.py +0 -0
  103. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_query_timeout_retry.py +0 -0
  104. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_readme_quickstart.py +0 -0
  105. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_redrive_replay.py +0 -0
  106. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_registration_ambiguity.py +0 -0
  107. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_release_metadata.py +0 -0
  108. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_replay.py +0 -0
  109. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_replay_clock.py +0 -0
  110. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_replay_conformance.py +0 -0
  111. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_replay_regression_corpus.py +0 -0
  112. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_replay_verify.py +0 -0
  113. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_retry_policy.py +0 -0
  114. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_run_cancellation_scope_replay.py +0 -0
  115. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_runtime_external_payload_transport.py +0 -0
  116. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_schedules.py +0 -0
  117. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_scoped_run_cancellation_context.py +0 -0
  118. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_serializer.py +0 -0
  119. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_signals.py +0 -0
  120. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_sleep.py +0 -0
  121. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_standalone_activity_client.py +0 -0
  122. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_sticky_execution.py +0 -0
  123. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_storage_admission.py +0 -0
  124. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_sync.py +0 -0
  125. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_testing_harness.py +0 -0
  126. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_update_signal_condition_replay.py +0 -0
  127. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_updates.py +0 -0
  128. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_wait_condition.py +0 -0
  129. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_worker.py +0 -0
  130. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_worker_observation.py +0 -0
  131. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_worker_session.py +0 -0
  132. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_workflow_entrypoint_validation.py +0 -0
  133. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_workflow_result_timeout.py +0 -0
  134. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_workflow_streams.py +0 -0
  135. {durable_workflow-2.6.0rc3 → durable_workflow-2.6.1}/tests/test_workflow_updates_conformance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.6.0rc3
3
+ Version: 2.6.1
4
4
  Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -139,6 +139,7 @@ for the complete cross-SDK contract.
139
139
  - [Python SDK guide](https://durable-workflow.com/docs/2.0/polyglot/python/)
140
140
  - [Complete SDK reference](docs/sdk-reference.md)
141
141
  - [Runnable examples](examples/)
142
+ - [Separate workflow and activity worker processes](docs/guides/separate-workers.md)
142
143
  - [Symmetric SDK playground](https://github.com/durable-workflow/sample-app#symmetric-sdk-playground)
143
144
 
144
145
  ## Runtime choices
@@ -99,6 +99,7 @@ for the complete cross-SDK contract.
99
99
  - [Python SDK guide](https://durable-workflow.com/docs/2.0/polyglot/python/)
100
100
  - [Complete SDK reference](docs/sdk-reference.md)
101
101
  - [Runnable examples](examples/)
102
+ - [Separate workflow and activity worker processes](docs/guides/separate-workers.md)
102
103
  - [Symmetric SDK playground](https://github.com/durable-workflow/sample-app#symmetric-sdk-playground)
103
104
 
104
105
  ## Runtime choices
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "durable-workflow"
7
- version = "2.6.0-rc.3"
7
+ version = "2.6.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"
@@ -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.6.0-rc.3"
75
- registry-version = "2.6.0rc3"
74
+ product-train = "2.6.1"
75
+ registry-version = "2.6.1"
76
76
  supported-server-versions = "2.8.0"
77
77
  worker-protocol-version = "1.20"
78
78
  control-plane-version = "2"
@@ -3527,9 +3527,18 @@ class Client:
3527
3527
  ``workflow_type`` is the language-neutral type key registered via
3528
3528
  :func:`durable_workflow.workflow.defn`. ``task_queue`` selects which
3529
3529
  worker pool picks up the workflow. ``workflow_id`` is the
3530
- caller-supplied instance id — if it collides with an existing
3531
- workflow, behavior depends on ``duplicate_policy``
3532
- (``reject`` | ``allow`` | ``terminate_existing``).
3530
+ caller-supplied instance ID. The PHP Server start contract accepts
3531
+ ``duplicate_policy="fail"`` (also the default when omitted) or
3532
+ ``"use-existing"``. ``fail`` raises :class:`WorkflowAlreadyStarted`
3533
+ if the ID already exists. ``use-existing`` returns the original run
3534
+ only while it is pending, running or waiting. Both policies reject
3535
+ a closed instance. Neither replaces a run or changes its input or
3536
+ deadlines. Use a new workflow ID for a new independent execution.
3537
+
3538
+ The SDK forwards the policy unchanged. ``reject``, ``allow`` and
3539
+ ``terminate_existing`` are not supported wire values. A resource-keyed
3540
+ workflow ID coordinates start admission; it does not revoke work
3541
+ already running in an external system.
3533
3542
 
3534
3543
  ``input`` is a list of positional arguments passed to the workflow's
3535
3544
  ``run`` method; the SDK encodes the list with the default payload
@@ -312,8 +312,10 @@ class WorkflowNotFound(DurableWorkflowError):
312
312
  class WorkflowAlreadyStarted(DurableWorkflowError):
313
313
  """A start request collided with an existing instance id.
314
314
 
315
- Raised when duplicate-start policy is ``reject`` (the default) and the
316
- caller-supplied ``workflow_id`` is already in use.
315
+ The PHP Server raises this for an existing ``workflow_id`` when the
316
+ duplicate policy is ``fail`` (the default), or when ``use-existing``
317
+ addresses a closed instance. ``use-existing`` returns a handle only
318
+ while the original run is pending, running or waiting.
317
319
  """
318
320
 
319
321
  def __init__(self, workflow_id: str) -> None:
@@ -874,7 +876,7 @@ def _raise_for_status(status: int, body: object, *, context: str = "") -> None:
874
876
  if status == 409:
875
877
  if reason == "schedule_already_exists":
876
878
  raise ScheduleAlreadyExists(context)
877
- if reason == "duplicate_not_allowed":
879
+ if reason in ("duplicate_not_allowed", "instance_already_started"):
878
880
  raise WorkflowAlreadyStarted(context)
879
881
  if reason == "run_not_active":
880
882
  if operation == "signal":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: durable-workflow
3
- Version: 2.6.0rc3
3
+ Version: 2.6.1
4
4
  Summary: Python client and worker SDK for Durable Workflow Cloud and self-hosted Server
5
5
  Author: Durable Workflow Contributors
6
6
  License-Expression: MIT
@@ -139,6 +139,7 @@ for the complete cross-SDK contract.
139
139
  - [Python SDK guide](https://durable-workflow.com/docs/2.0/polyglot/python/)
140
140
  - [Complete SDK reference](docs/sdk-reference.md)
141
141
  - [Runnable examples](examples/)
142
+ - [Separate workflow and activity worker processes](docs/guides/separate-workers.md)
142
143
  - [Symmetric SDK playground](https://github.com/durable-workflow/sample-app#symmetric-sdk-playground)
143
144
 
144
145
  ## Runtime choices
@@ -57,9 +57,11 @@ class TestRaiseForStatus:
57
57
  with pytest.raises(ServerError):
58
58
  _raise_for_status(404, {"reason": "other"})
59
59
 
60
- def test_409_duplicate(self) -> None:
61
- with pytest.raises(WorkflowAlreadyStarted):
62
- _raise_for_status(409, {"reason": "duplicate_not_allowed"}, context="wf-1")
60
+ @pytest.mark.parametrize("reason", ["duplicate_not_allowed", "instance_already_started"])
61
+ def test_409_duplicate(self, reason: str) -> None:
62
+ with pytest.raises(WorkflowAlreadyStarted) as duplicate:
63
+ _raise_for_status(409, {"reason": reason}, context="resource-017")
64
+ assert duplicate.value.workflow_id == "resource-017"
63
65
 
64
66
  def test_409_query_rejected(self) -> None:
65
67
  with pytest.raises(QueryFailed):