durable-workflow 0.4.75__tar.gz → 0.4.76__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-0.4.75/src/durable_workflow.egg-info → durable_workflow-0.4.76}/PKG-INFO +4 -2
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/README.md +3 -1
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/pyproject.toml +1 -1
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/python_conformance.py +241 -34
- {durable_workflow-0.4.75 → durable_workflow-0.4.76/src/durable_workflow.egg-info}/PKG-INFO +4 -2
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_python_conformance.py +86 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/LICENSE +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/setup.cfg +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/__init__.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/_avro.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/activity.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/auth_composition.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/client.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/errors.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/external_storage.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/external_task_input.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/external_task_result.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/history_bundle_verify.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/interceptors.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/invocable.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/metrics.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/py.typed +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/replay_conformance.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/replay_verify.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/retry_policy.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/serializer.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/sync.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/testing.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/worker.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/workflow.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/SOURCES.txt +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/dependency_links.txt +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/entry_points.txt +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/requires.txt +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/top_level.txt +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_activity_context.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_auth_composition.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_client.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_control_plane_parity_fixtures.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_errors.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_external_storage.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_external_task_input.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_external_task_result.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_golden_history_replay.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_history_bundle_verify.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_history_event_contract.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_invocable.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_metrics.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_order_processing_example.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_public_boundary_scanner.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_queries.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_readme_quickstart.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_replay.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_replay_conformance.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_replay_verify.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_retry_policy.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_schedules.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_serializer.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_signals.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_sleep.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_standalone_activity_client.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_sync.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_testing_harness.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_updates.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_wait_condition.py +0 -0
- {durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: durable-workflow
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.76
|
|
4
4
|
Summary: Python SDK for the Durable Workflow server (language-neutral HTTP protocol)
|
|
5
5
|
Author: Durable Workflow Contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -316,7 +316,9 @@ artifact versions, protocol traces, a no-PHP-assumption audit, and the complete
|
|
|
316
316
|
Python capability table. Host runners can feed their raw published-artifact
|
|
317
317
|
observations to `--compose`; omitted parity cells become explicit
|
|
318
318
|
`not_covered` entries so the gate reports the remaining scenario or capability
|
|
319
|
-
instead of accepting a smoke-only result.
|
|
319
|
+
instead of accepting a smoke-only result. The composer accepts canonical
|
|
320
|
+
snake_case IDs and runbook-style hyphenated IDs such as `server-up` and
|
|
321
|
+
`result-returned`.
|
|
320
322
|
|
|
321
323
|
## External payload storage
|
|
322
324
|
|
|
@@ -280,7 +280,9 @@ artifact versions, protocol traces, a no-PHP-assumption audit, and the complete
|
|
|
280
280
|
Python capability table. Host runners can feed their raw published-artifact
|
|
281
281
|
observations to `--compose`; omitted parity cells become explicit
|
|
282
282
|
`not_covered` entries so the gate reports the remaining scenario or capability
|
|
283
|
-
instead of accepting a smoke-only result.
|
|
283
|
+
instead of accepting a smoke-only result. The composer accepts canonical
|
|
284
|
+
snake_case IDs and runbook-style hyphenated IDs such as `server-up` and
|
|
285
|
+
`result-returned`.
|
|
284
286
|
|
|
285
287
|
## External payload storage
|
|
286
288
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "durable-workflow"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.76"
|
|
8
8
|
description = "Python SDK for the Durable Workflow server (language-neutral HTTP protocol)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/python_conformance.py
RENAMED
|
@@ -12,6 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
import argparse
|
|
13
13
|
import copy
|
|
14
14
|
import json
|
|
15
|
+
import re
|
|
15
16
|
import sys
|
|
16
17
|
from collections.abc import Iterable, Mapping
|
|
17
18
|
from pathlib import Path
|
|
@@ -70,6 +71,17 @@ PLACEHOLDER_VERSION_TOKENS = {
|
|
|
70
71
|
"unresolved",
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
|
|
75
|
+
def _normalize_identifier(value: str) -> str:
|
|
76
|
+
split = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", value)
|
|
77
|
+
normalized = re.sub(r"[^0-9A-Za-z]+", "_", split).strip("_").lower()
|
|
78
|
+
return re.sub(r"_+", "_", normalized)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _kebabize(value: str) -> str:
|
|
82
|
+
return _normalize_identifier(value).replace("_", "-")
|
|
83
|
+
|
|
84
|
+
|
|
73
85
|
REQUIRED_SCENARIOS = (
|
|
74
86
|
PUBLISHED_ARTIFACT_INSTALL_ONLY_SCENARIO,
|
|
75
87
|
"official_cli_install_start_result_path",
|
|
@@ -103,6 +115,35 @@ REQUIRED_CAPABILITIES = (
|
|
|
103
115
|
"php_assumptions_absent",
|
|
104
116
|
)
|
|
105
117
|
|
|
118
|
+
SCENARIO_ID_ALIASES: Mapping[str, tuple[str, ...]] = {
|
|
119
|
+
scenario: (_kebabize(scenario),)
|
|
120
|
+
for scenario in REQUIRED_SCENARIOS
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
CAPABILITY_ID_ALIASES: Mapping[str, tuple[str, ...]] = {
|
|
124
|
+
capability: (_kebabize(capability),)
|
|
125
|
+
for capability in REQUIRED_CAPABILITIES
|
|
126
|
+
}
|
|
127
|
+
CAPABILITY_ID_ALIASES = {
|
|
128
|
+
**CAPABILITY_ID_ALIASES,
|
|
129
|
+
"official_cli_installed": (
|
|
130
|
+
*CAPABILITY_ID_ALIASES["official_cli_installed"],
|
|
131
|
+
"cli-installed",
|
|
132
|
+
"cli_installed",
|
|
133
|
+
),
|
|
134
|
+
"cli_reads_workflow_result": (
|
|
135
|
+
*CAPABILITY_ID_ALIASES["cli_reads_workflow_result"],
|
|
136
|
+
"cli-reads-result",
|
|
137
|
+
"cli_result",
|
|
138
|
+
"cli-result",
|
|
139
|
+
),
|
|
140
|
+
"workflow_result_returned": (
|
|
141
|
+
*CAPABILITY_ID_ALIASES["workflow_result_returned"],
|
|
142
|
+
"result-returned",
|
|
143
|
+
"result_returned",
|
|
144
|
+
),
|
|
145
|
+
}
|
|
146
|
+
|
|
106
147
|
SCENARIO_REQUIRED_EVIDENCE: Mapping[str, tuple[str, ...]] = {
|
|
107
148
|
"published_artifact_install_only": (
|
|
108
149
|
"install_channels",
|
|
@@ -267,6 +308,16 @@ def host_evidence_spec() -> dict[str, Any]:
|
|
|
267
308
|
"capability_table": list(CAPABILITY_TABLE_FIELDS),
|
|
268
309
|
"declared_outcome": list(DECLARED_OUTCOME_FIELDS),
|
|
269
310
|
},
|
|
311
|
+
"entry_id_aliases": {
|
|
312
|
+
"scenario_ids": {
|
|
313
|
+
scenario: list(SCENARIO_ID_ALIASES.get(scenario, ()))
|
|
314
|
+
for scenario in REQUIRED_SCENARIOS
|
|
315
|
+
},
|
|
316
|
+
"capability_ids": {
|
|
317
|
+
capability: list(CAPABILITY_ID_ALIASES.get(capability, ()))
|
|
318
|
+
for capability in REQUIRED_CAPABILITIES
|
|
319
|
+
},
|
|
320
|
+
},
|
|
270
321
|
"top_level_evidence_fields": [
|
|
271
322
|
"install_channels",
|
|
272
323
|
"source_policy",
|
|
@@ -353,7 +404,7 @@ def compose_result(evidence: Mapping[str, Any], contract: Mapping[str, Any] | No
|
|
|
353
404
|
"finding_links": _deepcopy_json_like(_first_present(evidence, ("finding_links", "findingLinks"))),
|
|
354
405
|
}
|
|
355
406
|
|
|
356
|
-
declared =
|
|
407
|
+
declared = _status_value_or_raw(_first_present(evidence, DECLARED_OUTCOME_FIELDS))
|
|
357
408
|
result["outcome"] = declared or _composed_outcome(result, contract)
|
|
358
409
|
return result
|
|
359
410
|
|
|
@@ -365,8 +416,13 @@ def _compose_scenario_results(
|
|
|
365
416
|
source_policy: Mapping[str, Any],
|
|
366
417
|
) -> dict[str, dict[str, Any]]:
|
|
367
418
|
duplicates: dict[str, int] = {}
|
|
368
|
-
raw_scenarios = _entries_by_id(evidence, SCENARIO_RESULTS_FIELDS, duplicates)
|
|
369
419
|
required_scenarios = _string_list(contract.get("required_scenarios", []))
|
|
420
|
+
raw_scenarios = _entries_by_id(
|
|
421
|
+
evidence,
|
|
422
|
+
SCENARIO_RESULTS_FIELDS,
|
|
423
|
+
duplicates,
|
|
424
|
+
canonical_aliases=_entry_aliases(required_scenarios, SCENARIO_ID_ALIASES),
|
|
425
|
+
)
|
|
370
426
|
required_evidence = _scenario_required_evidence(contract)
|
|
371
427
|
scenario_results: dict[str, dict[str, Any]] = {}
|
|
372
428
|
|
|
@@ -374,14 +430,14 @@ def _compose_scenario_results(
|
|
|
374
430
|
scenario = _copy_mapping(raw_scenarios.get(scenario_id))
|
|
375
431
|
scenario["scenario_id"] = scenario_id
|
|
376
432
|
_apply_top_level_scenario_evidence(scenario_id, scenario, evidence, artifacts, source_policy, contract)
|
|
377
|
-
status =
|
|
433
|
+
status = _status_value_or_raw(scenario.get("status"), scenario.get("result"), scenario.get("outcome"))
|
|
378
434
|
if status == "":
|
|
379
435
|
status = (
|
|
380
436
|
"pass"
|
|
381
437
|
if _scenario_has_required_evidence(scenario, required_evidence.get(scenario_id, []))
|
|
382
438
|
else "not_covered"
|
|
383
439
|
)
|
|
384
|
-
|
|
440
|
+
scenario["status"] = status
|
|
385
441
|
if status == "pass" and not _has_observed_outputs(scenario):
|
|
386
442
|
scenario["observed_outputs"] = {
|
|
387
443
|
"summary": "required Python SDK conformance evidence recorded",
|
|
@@ -495,23 +551,22 @@ def _apply_top_level_scenario_evidence(
|
|
|
495
551
|
|
|
496
552
|
|
|
497
553
|
def _compose_capability_table(evidence: Mapping[str, Any], contract: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
498
|
-
raw_capabilities = _raw_capability_entries(evidence)
|
|
499
554
|
required_capabilities = _required_capabilities(contract)
|
|
555
|
+
raw_capabilities = _raw_capability_entries(evidence, contract)
|
|
500
556
|
capability_table: list[dict[str, Any]] = []
|
|
501
557
|
|
|
502
558
|
for capability_id in required_capabilities:
|
|
503
559
|
capability = _copy_mapping(raw_capabilities.get(capability_id))
|
|
504
560
|
capability["id"] = capability_id
|
|
505
|
-
status =
|
|
506
|
-
capability.get("status")
|
|
507
|
-
|
|
508
|
-
|
|
561
|
+
status = _status_value_or_raw(
|
|
562
|
+
capability.get("status"),
|
|
563
|
+
capability.get("result"),
|
|
564
|
+
capability.get("outcome"),
|
|
565
|
+
capability.get("verdict"),
|
|
509
566
|
)
|
|
510
567
|
if status == "":
|
|
511
568
|
status = "not_covered"
|
|
512
|
-
|
|
513
|
-
else:
|
|
514
|
-
capability.setdefault("status", status)
|
|
569
|
+
capability["status"] = status
|
|
515
570
|
capability_table.append(capability)
|
|
516
571
|
|
|
517
572
|
for capability_id, capability in raw_capabilities.items():
|
|
@@ -524,8 +579,10 @@ def _compose_capability_table(evidence: Mapping[str, Any], contract: Mapping[str
|
|
|
524
579
|
return capability_table
|
|
525
580
|
|
|
526
581
|
|
|
527
|
-
def _raw_capability_entries(evidence: Mapping[str, Any]) -> dict[str, dict[str, Any]]:
|
|
582
|
+
def _raw_capability_entries(evidence: Mapping[str, Any], contract: Mapping[str, Any]) -> dict[str, dict[str, Any]]:
|
|
528
583
|
raw = _first_present(evidence, CAPABILITY_TABLE_FIELDS)
|
|
584
|
+
required_capabilities = _required_capabilities(contract)
|
|
585
|
+
aliases = _entry_aliases(required_capabilities, CAPABILITY_ID_ALIASES)
|
|
529
586
|
if isinstance(raw, Mapping):
|
|
530
587
|
entries: dict[str, dict[str, Any]] = {}
|
|
531
588
|
for key, value in raw.items():
|
|
@@ -539,12 +596,18 @@ def _raw_capability_entries(evidence: Mapping[str, Any]) -> dict[str, dict[str,
|
|
|
539
596
|
entry = {"status": value}
|
|
540
597
|
else:
|
|
541
598
|
continue
|
|
542
|
-
|
|
543
|
-
|
|
599
|
+
capability_id = _canonical_entry_id(key, aliases)
|
|
600
|
+
entry.setdefault("id", capability_id)
|
|
601
|
+
entries[capability_id] = entry
|
|
544
602
|
return entries
|
|
545
603
|
|
|
546
604
|
duplicates: dict[str, int] = {}
|
|
547
|
-
return _entries_by_id(
|
|
605
|
+
return _entries_by_id(
|
|
606
|
+
evidence,
|
|
607
|
+
CAPABILITY_TABLE_FIELDS,
|
|
608
|
+
duplicates,
|
|
609
|
+
canonical_aliases=aliases,
|
|
610
|
+
)
|
|
548
611
|
|
|
549
612
|
|
|
550
613
|
def _scenario_has_required_evidence(scenario: Mapping[str, Any], required_fields: Iterable[str]) -> bool:
|
|
@@ -562,13 +625,18 @@ def _attach_scenario_findings(scenario: dict[str, Any], evidence: Mapping[str, A
|
|
|
562
625
|
links = _first_present(evidence, ("finding_links", "findingLinks", "findings"))
|
|
563
626
|
linked: Any = None
|
|
564
627
|
if isinstance(links, Mapping):
|
|
565
|
-
linked = links
|
|
628
|
+
linked = _entry_mapping_value(links, scenario_id, SCENARIO_ID_ALIASES)
|
|
566
629
|
elif isinstance(links, list):
|
|
630
|
+
aliases = _entry_aliases((scenario_id,), SCENARIO_ID_ALIASES)
|
|
567
631
|
linked = [
|
|
568
632
|
item
|
|
569
633
|
for item in links
|
|
570
634
|
if isinstance(item, Mapping)
|
|
571
|
-
and
|
|
635
|
+
and _canonical_entry_id(
|
|
636
|
+
_string_value(item.get("scenario_id") or item.get("scenario") or item.get("id")),
|
|
637
|
+
aliases,
|
|
638
|
+
)
|
|
639
|
+
== _canonical_entry_id(scenario_id, aliases)
|
|
572
640
|
]
|
|
573
641
|
if linked not in (None, "", [], {}):
|
|
574
642
|
scenario["linked_findings"] = _deepcopy_json_like(linked)
|
|
@@ -581,10 +649,32 @@ def _filtered_traces(traces: Any, plane: str) -> Any:
|
|
|
581
649
|
trace
|
|
582
650
|
for trace in traces
|
|
583
651
|
if isinstance(trace, Mapping)
|
|
584
|
-
and
|
|
652
|
+
and _trace_plane_matches(trace.get("plane"), plane)
|
|
585
653
|
]
|
|
586
654
|
|
|
587
655
|
|
|
656
|
+
def _trace_plane_matches(value: Any, plane: str) -> bool:
|
|
657
|
+
normalized = _normalize_identifier(_string_value(value))
|
|
658
|
+
if plane == "control":
|
|
659
|
+
return normalized in {
|
|
660
|
+
"control",
|
|
661
|
+
"control_plane",
|
|
662
|
+
"control_protocol",
|
|
663
|
+
"control_plane_protocol",
|
|
664
|
+
"api",
|
|
665
|
+
"cli",
|
|
666
|
+
"client",
|
|
667
|
+
}
|
|
668
|
+
if plane == "worker":
|
|
669
|
+
return normalized in {
|
|
670
|
+
"worker",
|
|
671
|
+
"worker_plane",
|
|
672
|
+
"worker_protocol",
|
|
673
|
+
"worker_plane_protocol",
|
|
674
|
+
}
|
|
675
|
+
return normalized == _normalize_identifier(plane)
|
|
676
|
+
|
|
677
|
+
|
|
588
678
|
def _timestamp_value(evidence: Mapping[str, Any], field: str) -> str:
|
|
589
679
|
return _string_value(_first_present(evidence, (field, _camelize(field))))
|
|
590
680
|
|
|
@@ -620,7 +710,12 @@ def evaluate_result(result: Mapping[str, Any], contract: Mapping[str, Any] | Non
|
|
|
620
710
|
required_capabilities = _required_capabilities(contract)
|
|
621
711
|
|
|
622
712
|
duplicate_scenarios: dict[str, int] = {}
|
|
623
|
-
scenario_results = _entries_by_id(
|
|
713
|
+
scenario_results = _entries_by_id(
|
|
714
|
+
result,
|
|
715
|
+
SCENARIO_RESULTS_FIELDS,
|
|
716
|
+
duplicate_scenarios,
|
|
717
|
+
canonical_aliases=_entry_aliases(required_scenarios, SCENARIO_ID_ALIASES),
|
|
718
|
+
)
|
|
624
719
|
scenario_statuses: dict[str, str] = {}
|
|
625
720
|
missing_scenarios: list[str] = []
|
|
626
721
|
non_pass_scenarios: list[str] = []
|
|
@@ -636,7 +731,12 @@ def evaluate_result(result: Mapping[str, Any], contract: Mapping[str, Any] | Non
|
|
|
636
731
|
failures.append({"code": "missing_required_scenario", "scenario_id": scenario_id})
|
|
637
732
|
continue
|
|
638
733
|
|
|
639
|
-
status =
|
|
734
|
+
status = _status_value_or_raw(
|
|
735
|
+
scenario_result.get("status"),
|
|
736
|
+
scenario_result.get("result"),
|
|
737
|
+
scenario_result.get("outcome"),
|
|
738
|
+
scenario_result.get("verdict"),
|
|
739
|
+
)
|
|
640
740
|
scenario_statuses[scenario_id] = status
|
|
641
741
|
if status not in allowed_statuses:
|
|
642
742
|
failures.append(
|
|
@@ -665,7 +765,12 @@ def evaluate_result(result: Mapping[str, Any], contract: Mapping[str, Any] | Non
|
|
|
665
765
|
non_pass_scenarios.append(scenario_id)
|
|
666
766
|
|
|
667
767
|
for scenario_id, scenario_result in scenario_results.items():
|
|
668
|
-
status =
|
|
768
|
+
status = _status_value_or_raw(
|
|
769
|
+
scenario_result.get("status"),
|
|
770
|
+
scenario_result.get("result"),
|
|
771
|
+
scenario_result.get("outcome"),
|
|
772
|
+
scenario_result.get("verdict"),
|
|
773
|
+
)
|
|
669
774
|
if status in NON_PASS_SCENARIO_STATUSES and not _has_linked_findings(scenario_result, result):
|
|
670
775
|
failures.append(
|
|
671
776
|
{
|
|
@@ -689,6 +794,7 @@ def evaluate_result(result: Mapping[str, Any], contract: Mapping[str, Any] | Non
|
|
|
689
794
|
result,
|
|
690
795
|
CAPABILITY_TABLE_FIELDS,
|
|
691
796
|
duplicate_capabilities,
|
|
797
|
+
canonical_aliases=_entry_aliases(required_capabilities, CAPABILITY_ID_ALIASES),
|
|
692
798
|
)
|
|
693
799
|
capability_statuses: dict[str, str] = {}
|
|
694
800
|
missing_capabilities: list[str] = []
|
|
@@ -704,7 +810,12 @@ def evaluate_result(result: Mapping[str, Any], contract: Mapping[str, Any] | Non
|
|
|
704
810
|
failures.append({"code": "missing_required_capability", "capability_id": capability_id})
|
|
705
811
|
continue
|
|
706
812
|
|
|
707
|
-
status =
|
|
813
|
+
status = _status_value_or_raw(
|
|
814
|
+
capability.get("status"),
|
|
815
|
+
capability.get("result"),
|
|
816
|
+
capability.get("outcome"),
|
|
817
|
+
capability.get("verdict"),
|
|
818
|
+
)
|
|
708
819
|
capability_statuses[capability_id] = status
|
|
709
820
|
if status != "pass":
|
|
710
821
|
non_pass_capabilities.append(capability_id)
|
|
@@ -793,9 +904,12 @@ def _entries_by_id(
|
|
|
793
904
|
result: Mapping[str, Any],
|
|
794
905
|
field_names: Iterable[str],
|
|
795
906
|
duplicates: dict[str, int],
|
|
907
|
+
*,
|
|
908
|
+
canonical_aliases: Mapping[str, str] | None = None,
|
|
796
909
|
) -> dict[str, dict[str, Any]]:
|
|
797
910
|
entries: dict[str, dict[str, Any]] = {}
|
|
798
911
|
seen: set[str] = set()
|
|
912
|
+
aliases = canonical_aliases or {}
|
|
799
913
|
raw = _first_present(result, field_names)
|
|
800
914
|
if isinstance(raw, Mapping):
|
|
801
915
|
iterable: Iterable[tuple[Any, Any]] = raw.items()
|
|
@@ -813,7 +927,8 @@ def _entries_by_id(
|
|
|
813
927
|
entry = {"status": value}
|
|
814
928
|
else:
|
|
815
929
|
continue
|
|
816
|
-
|
|
930
|
+
raw_entry_id = key if isinstance(key, str) else _entry_id(entry)
|
|
931
|
+
entry_id = _canonical_entry_id(raw_entry_id, aliases) if isinstance(raw_entry_id, str) else ""
|
|
817
932
|
if not isinstance(entry_id, str) or entry_id == "":
|
|
818
933
|
continue
|
|
819
934
|
if entry_id in seen:
|
|
@@ -828,12 +943,52 @@ def _entry_id(entry: Mapping[str, Any]) -> str:
|
|
|
828
943
|
return _string_value(
|
|
829
944
|
entry.get("scenario_id")
|
|
830
945
|
or entry.get("scenarioId")
|
|
946
|
+
or entry.get("scenario")
|
|
947
|
+
or entry.get("scenario_name")
|
|
948
|
+
or entry.get("scenarioName")
|
|
949
|
+
or entry.get("case_id")
|
|
950
|
+
or entry.get("caseId")
|
|
831
951
|
or entry.get("capability_id")
|
|
832
952
|
or entry.get("capabilityId")
|
|
953
|
+
or entry.get("capability")
|
|
954
|
+
or entry.get("capability_name")
|
|
955
|
+
or entry.get("capabilityName")
|
|
833
956
|
or entry.get("id")
|
|
957
|
+
or entry.get("name")
|
|
958
|
+
or entry.get("key")
|
|
834
959
|
)
|
|
835
960
|
|
|
836
961
|
|
|
962
|
+
def _entry_aliases(
|
|
963
|
+
canonical_ids: Iterable[str],
|
|
964
|
+
explicit_aliases: Mapping[str, Iterable[str]],
|
|
965
|
+
) -> dict[str, str]:
|
|
966
|
+
aliases: dict[str, str] = {}
|
|
967
|
+
for canonical_id in canonical_ids:
|
|
968
|
+
aliases[_normalize_identifier(canonical_id)] = canonical_id
|
|
969
|
+
for alias in explicit_aliases.get(canonical_id, ()):
|
|
970
|
+
aliases[_normalize_identifier(alias)] = canonical_id
|
|
971
|
+
return aliases
|
|
972
|
+
|
|
973
|
+
|
|
974
|
+
def _canonical_entry_id(entry_id: str, aliases: Mapping[str, str]) -> str:
|
|
975
|
+
normalized = _normalize_identifier(entry_id)
|
|
976
|
+
return aliases.get(normalized, normalized)
|
|
977
|
+
|
|
978
|
+
|
|
979
|
+
def _entry_mapping_value(
|
|
980
|
+
values: Mapping[Any, Any],
|
|
981
|
+
canonical_id: str,
|
|
982
|
+
explicit_aliases: Mapping[str, Iterable[str]],
|
|
983
|
+
) -> Any:
|
|
984
|
+
aliases = _entry_aliases((canonical_id,), explicit_aliases)
|
|
985
|
+
target = _canonical_entry_id(canonical_id, aliases)
|
|
986
|
+
for key, value in values.items():
|
|
987
|
+
if isinstance(key, str) and _canonical_entry_id(key, aliases) == target:
|
|
988
|
+
return value
|
|
989
|
+
return None
|
|
990
|
+
|
|
991
|
+
|
|
837
992
|
def _run_record_failures(result: Mapping[str, Any], contract: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
838
993
|
required = _string_list(
|
|
839
994
|
_mapping_value(contract.get("artifact_policy")).get("required_run_record_fields", [])
|
|
@@ -917,7 +1072,8 @@ def _source_policy_failures(
|
|
|
917
1072
|
scenario_id=scenario_id,
|
|
918
1073
|
require_published_artifact_policy=(
|
|
919
1074
|
scenario_id == PUBLISHED_ARTIFACT_INSTALL_ONLY_SCENARIO
|
|
920
|
-
and
|
|
1075
|
+
and _status_value_or_raw(scenario.get("status"), scenario.get("result"), scenario.get("outcome"))
|
|
1076
|
+
== "pass"
|
|
921
1077
|
),
|
|
922
1078
|
)
|
|
923
1079
|
)
|
|
@@ -1088,7 +1244,7 @@ def _php_assumption_audit_failures(
|
|
|
1088
1244
|
if not audit:
|
|
1089
1245
|
return [{"code": "missing_php_assumption_audit"}]
|
|
1090
1246
|
|
|
1091
|
-
status =
|
|
1247
|
+
status = _status_value_or_raw(audit.get("status"), audit.get("outcome"), audit.get("verdict"))
|
|
1092
1248
|
failures: list[dict[str, Any]] = []
|
|
1093
1249
|
if status != "pass":
|
|
1094
1250
|
failures.append({"code": "non_pass_php_assumption_audit", "status": status})
|
|
@@ -1101,7 +1257,7 @@ def _php_assumption_audit_failures(
|
|
|
1101
1257
|
"no_php_only_error_shapes",
|
|
1102
1258
|
)
|
|
1103
1259
|
for check in required_checks:
|
|
1104
|
-
if checks
|
|
1260
|
+
if not _pass_bool_value(_first_present(checks, (check, _camelize(check), _kebabize(check)))):
|
|
1105
1261
|
failures.append({"code": "missing_php_assumption_check", "check": check})
|
|
1106
1262
|
return failures
|
|
1107
1263
|
|
|
@@ -1236,9 +1392,7 @@ def _sdk_runtime_audit_fields() -> tuple[str, ...]:
|
|
|
1236
1392
|
|
|
1237
1393
|
|
|
1238
1394
|
def _declared_outcome_failures(result: Mapping[str, Any], evaluated_status: str) -> list[dict[str, Any]]:
|
|
1239
|
-
declared =
|
|
1240
|
-
_first_present(result, DECLARED_OUTCOME_FIELDS)
|
|
1241
|
-
)
|
|
1395
|
+
declared = _status_value_or_raw(_first_present(result, DECLARED_OUTCOME_FIELDS))
|
|
1242
1396
|
if declared == "":
|
|
1243
1397
|
return [{"code": "missing_declared_outcome"}]
|
|
1244
1398
|
if _normal_outcome(declared) != evaluated_status:
|
|
@@ -1253,7 +1407,7 @@ def _declared_outcome_failures(result: Mapping[str, Any], evaluated_status: str)
|
|
|
1253
1407
|
|
|
1254
1408
|
|
|
1255
1409
|
def _normal_outcome(value: str) -> str:
|
|
1256
|
-
return "pass" if value == "pass" else "non_passing"
|
|
1410
|
+
return "pass" if _status_value(value) == "pass" else "non_passing"
|
|
1257
1411
|
|
|
1258
1412
|
|
|
1259
1413
|
def _has_observed_outputs(entry: Mapping[str, Any]) -> bool:
|
|
@@ -1284,7 +1438,7 @@ def _has_linked_findings(scenario_result: Mapping[str, Any], result: Mapping[str
|
|
|
1284
1438
|
for field in ("finding_links", "findingLinks", "findings"):
|
|
1285
1439
|
links = _first_present(result, (field,))
|
|
1286
1440
|
if isinstance(links, Mapping):
|
|
1287
|
-
value = links
|
|
1441
|
+
value = _entry_mapping_value(links, scenario_id, SCENARIO_ID_ALIASES)
|
|
1288
1442
|
if value not in (None, "", [], {}):
|
|
1289
1443
|
return True
|
|
1290
1444
|
elif isinstance(links, list):
|
|
@@ -1292,7 +1446,8 @@ def _has_linked_findings(scenario_result: Mapping[str, Any], result: Mapping[str
|
|
|
1292
1446
|
if not isinstance(item, Mapping):
|
|
1293
1447
|
continue
|
|
1294
1448
|
linked = _string_value(item.get("scenario_id") or item.get("scenario") or item.get("id"))
|
|
1295
|
-
|
|
1449
|
+
aliases = _entry_aliases((scenario_id,), SCENARIO_ID_ALIASES)
|
|
1450
|
+
if _canonical_entry_id(linked, aliases) == _canonical_entry_id(scenario_id, aliases):
|
|
1296
1451
|
return True
|
|
1297
1452
|
return False
|
|
1298
1453
|
|
|
@@ -1337,6 +1492,58 @@ def _string_list(value: Any) -> list[str]:
|
|
|
1337
1492
|
return [item for item in value if isinstance(item, str)]
|
|
1338
1493
|
|
|
1339
1494
|
|
|
1495
|
+
def _status_value(*values: Any) -> str:
|
|
1496
|
+
for value in values:
|
|
1497
|
+
normalized = _normal_status_value(value)
|
|
1498
|
+
if normalized:
|
|
1499
|
+
return normalized
|
|
1500
|
+
return ""
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
def _status_value_or_raw(*values: Any) -> str:
|
|
1504
|
+
normalized = _status_value(*values)
|
|
1505
|
+
if normalized:
|
|
1506
|
+
return normalized
|
|
1507
|
+
for value in values:
|
|
1508
|
+
raw = _string_value(value)
|
|
1509
|
+
if raw:
|
|
1510
|
+
return raw
|
|
1511
|
+
return ""
|
|
1512
|
+
|
|
1513
|
+
|
|
1514
|
+
def _normal_status_value(value: Any) -> str:
|
|
1515
|
+
if isinstance(value, bool):
|
|
1516
|
+
return "pass" if value else "fail"
|
|
1517
|
+
normalized = _normalize_identifier(_string_value(value))
|
|
1518
|
+
return {
|
|
1519
|
+
"ok": "pass",
|
|
1520
|
+
"true": "pass",
|
|
1521
|
+
"yes": "pass",
|
|
1522
|
+
"passed": "pass",
|
|
1523
|
+
"pass": "pass",
|
|
1524
|
+
"success": "pass",
|
|
1525
|
+
"successful": "pass",
|
|
1526
|
+
"succeeded": "pass",
|
|
1527
|
+
"failed": "fail",
|
|
1528
|
+
"false": "fail",
|
|
1529
|
+
"no": "fail",
|
|
1530
|
+
"failure": "fail",
|
|
1531
|
+
"fail": "fail",
|
|
1532
|
+
"error": "fail",
|
|
1533
|
+
"non_pass": "fail",
|
|
1534
|
+
"non_passing": "fail",
|
|
1535
|
+
"unsupported": "unsupported",
|
|
1536
|
+
"not_covered": "not_covered",
|
|
1537
|
+
"uncovered": "not_covered",
|
|
1538
|
+
"runner_blocked": "runner_blocked",
|
|
1539
|
+
"blocked": "runner_blocked",
|
|
1540
|
+
}.get(normalized, "")
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
def _pass_bool_value(value: Any) -> bool:
|
|
1544
|
+
return value is True or _status_value(value) == "pass"
|
|
1545
|
+
|
|
1546
|
+
|
|
1340
1547
|
def _string_value(value: Any) -> str:
|
|
1341
1548
|
if isinstance(value, str):
|
|
1342
1549
|
return value
|
|
@@ -1346,7 +1553,7 @@ def _string_value(value: Any) -> str:
|
|
|
1346
1553
|
|
|
1347
1554
|
|
|
1348
1555
|
def _has_non_empty_field(mapping: Mapping[str, Any], field: str) -> bool:
|
|
1349
|
-
value = _first_present(mapping, (field, _camelize(field)))
|
|
1556
|
+
value = _first_present(mapping, (field, _camelize(field), _kebabize(field)))
|
|
1350
1557
|
return value not in (None, "", [], {})
|
|
1351
1558
|
|
|
1352
1559
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: durable-workflow
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.76
|
|
4
4
|
Summary: Python SDK for the Durable Workflow server (language-neutral HTTP protocol)
|
|
5
5
|
Author: Durable Workflow Contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -316,7 +316,9 @@ artifact versions, protocol traces, a no-PHP-assumption audit, and the complete
|
|
|
316
316
|
Python capability table. Host runners can feed their raw published-artifact
|
|
317
317
|
observations to `--compose`; omitted parity cells become explicit
|
|
318
318
|
`not_covered` entries so the gate reports the remaining scenario or capability
|
|
319
|
-
instead of accepting a smoke-only result.
|
|
319
|
+
instead of accepting a smoke-only result. The composer accepts canonical
|
|
320
|
+
snake_case IDs and runbook-style hyphenated IDs such as `server-up` and
|
|
321
|
+
`result-returned`.
|
|
320
322
|
|
|
321
323
|
## External payload storage
|
|
322
324
|
|
|
@@ -211,6 +211,7 @@ def test_manifest_names_full_python_parity_surface() -> None:
|
|
|
211
211
|
assert "capability_table_complete" in payload["required_scenarios"]
|
|
212
212
|
assert payload["host_evidence"]["schema"] == "durable-workflow.v2.python-sdk-parity.host-evidence"
|
|
213
213
|
assert payload["host_evidence"]["missing_scenario_status"] == "not_covered"
|
|
214
|
+
assert "server-up" in payload["host_evidence"]["entry_id_aliases"]["capability_ids"]["server_up"]
|
|
214
215
|
|
|
215
216
|
|
|
216
217
|
def test_evaluate_result_accepts_complete_published_artifact_evidence() -> None:
|
|
@@ -307,6 +308,91 @@ def test_compose_result_accepts_runner_native_host_evidence_aliases() -> None:
|
|
|
307
308
|
assert evaluation["gate_failures"] == []
|
|
308
309
|
|
|
309
310
|
|
|
311
|
+
def test_compose_result_accepts_runbook_style_ids_and_statuses() -> None:
|
|
312
|
+
result = complete_result()
|
|
313
|
+
derived_scenarios = {
|
|
314
|
+
"published_artifact_install_only",
|
|
315
|
+
"official_cli_install_start_result_path",
|
|
316
|
+
"cold_first_user_setup",
|
|
317
|
+
"protocol_trace_capture",
|
|
318
|
+
"php_assumption_audit",
|
|
319
|
+
"capability_table_complete",
|
|
320
|
+
}
|
|
321
|
+
scenario_evidence = []
|
|
322
|
+
for scenario in REQUIRED_SCENARIOS:
|
|
323
|
+
if scenario in derived_scenarios:
|
|
324
|
+
continue
|
|
325
|
+
entry = dict(result["scenario_results"][scenario])
|
|
326
|
+
entry.pop("scenario_id", None)
|
|
327
|
+
entry["scenario"] = scenario.replace("_", "-")
|
|
328
|
+
entry["status"] = "succeeded"
|
|
329
|
+
scenario_evidence.append(entry)
|
|
330
|
+
|
|
331
|
+
def capability_alias(capability: str) -> str:
|
|
332
|
+
if capability == "official_cli_installed":
|
|
333
|
+
return "cli-installed"
|
|
334
|
+
if capability == "workflow_result_returned":
|
|
335
|
+
return "result-returned"
|
|
336
|
+
return capability.replace("_", "-")
|
|
337
|
+
|
|
338
|
+
evidence = {
|
|
339
|
+
"startedAt": result["started_at"],
|
|
340
|
+
"finishedAt": result["finished_at"],
|
|
341
|
+
"generatedAt": result["generated_at"],
|
|
342
|
+
"artifactVersions": result["artifact_versions"],
|
|
343
|
+
"sourcePolicy": result["source_policy"],
|
|
344
|
+
"installChannels": result["scenario_results"]["published_artifact_install_only"]["install_channels"],
|
|
345
|
+
"officialCli": {
|
|
346
|
+
"installCommand": "curl -fsSL https://durable-workflow.com/install.sh | sh",
|
|
347
|
+
"startCommand": "dw workflow:start --json",
|
|
348
|
+
"resultCommand": "dw workflow:result --json",
|
|
349
|
+
"jsonOutputs": [{"workflow_id": "py-parity", "status": "completed"}],
|
|
350
|
+
},
|
|
351
|
+
"firstUserFlow": {
|
|
352
|
+
"freshState": True,
|
|
353
|
+
"namespaceCreated": "default",
|
|
354
|
+
"firstWorkflowStarted": "py-parity",
|
|
355
|
+
"resultObserved": {"status": "completed"},
|
|
356
|
+
},
|
|
357
|
+
"traces": [
|
|
358
|
+
{"plane": "control-plane", "request": "POST /workflows", "response": 201},
|
|
359
|
+
{"plane": "worker-protocol", "request": "POST /worker/workflow-tasks/poll", "response": 200},
|
|
360
|
+
],
|
|
361
|
+
"languageNeutralityAudit": {
|
|
362
|
+
"status": "succeeded",
|
|
363
|
+
"serverAudit": {"status": "ok"},
|
|
364
|
+
"sdkAudit": {"status": "ok"},
|
|
365
|
+
"checks": {
|
|
366
|
+
"noPhpRuntimeRequired": True,
|
|
367
|
+
"noPhpPathsRequired": True,
|
|
368
|
+
"noPhpSerializerRequired": True,
|
|
369
|
+
"noPhpOnlyErrorShapes": True,
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
"scenarioEvidence": scenario_evidence,
|
|
373
|
+
"capabilityResults": [
|
|
374
|
+
{
|
|
375
|
+
"capability": capability_alias(capability),
|
|
376
|
+
"status": "passed",
|
|
377
|
+
"evidence": {"observed": True},
|
|
378
|
+
}
|
|
379
|
+
for capability in REQUIRED_CAPABILITIES
|
|
380
|
+
],
|
|
381
|
+
"findings": [],
|
|
382
|
+
"findingLinks": [],
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
composed = compose_result(evidence)
|
|
386
|
+
evaluation = evaluate_result(composed)
|
|
387
|
+
|
|
388
|
+
assert composed["outcome"] == "pass"
|
|
389
|
+
assert composed["scenario_results"]["worker_restart_activity_and_signal_state"]["status"] == "pass"
|
|
390
|
+
assert composed["scenario_results"]["protocol_trace_capture"]["worker_protocol_traces"] == [evidence["traces"][1]]
|
|
391
|
+
assert {entry["id"] for entry in composed["capability_table"]} == set(REQUIRED_CAPABILITIES)
|
|
392
|
+
assert evaluation["status"] == "pass"
|
|
393
|
+
assert evaluation["gate_failures"] == []
|
|
394
|
+
|
|
395
|
+
|
|
310
396
|
def test_compose_result_rejects_protocol_trace_evidence_without_both_planes() -> None:
|
|
311
397
|
evidence = complete_host_evidence()
|
|
312
398
|
evidence["protocol_traces"] = [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/auth_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/external_storage.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/external_task_input.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/external_task_result.py
RENAMED
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/history_bundle_verify.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow/replay_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
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/requires.txt
RENAMED
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/src/durable_workflow.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{durable_workflow-0.4.75 → durable_workflow-0.4.76}/tests/test_control_plane_parity_fixtures.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-0.4.75 → durable_workflow-0.4.76}/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
|