File tree Expand file tree Collapse file tree
Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -203,6 +203,28 @@ infers the workflow type and input from that event; otherwise pass
203203contains the commands the workflow would emit next, including determinism
204204failures surfaced as workflow failure commands.
205205
206+ For CI and operator replay gates, the package also installs offline
207+ verification commands:
208+
209+ ``` bash
210+ durable-workflow-replay-verify tests/fixtures/golden_history \
211+ --workflows my_app.workflows:all_workflows \
212+ --output replay-report.json
213+
214+ durable-workflow-replay-verify exported-history-bundles \
215+ --simulate-bundles \
216+ --output replay-simulation.json
217+
218+ durable-workflow-history-bundle-verify exported-history-bundles/run-001.json \
219+ --output integrity-report.json
220+ ```
221+
222+ ` durable-workflow-replay-verify ` emits the same verdict and
223+ ` promotion_decision ` vocabulary as the platform replay contract. Golden-history
224+ mode replays cross-runtime fixtures against registered workflow classes;
225+ ` --simulate-bundles ` integrity-checks every exported history bundle in a
226+ directory and reports missing bundle evidence as a blocking result.
227+
206228## External payload storage
207229
208230Large payload offload is opt-in. ` serializer.external_storage_envelope(...) `
Original file line number Diff line number Diff line change @@ -56,6 +56,10 @@ Documentation = "https://durable-workflow.github.io/docs/2.0/polyglot/python"
5656Repository = " https://github.com/durable-workflow/sdk-python"
5757Issues = " https://github.com/durable-workflow/sdk-python/issues"
5858
59+ [project .scripts ]
60+ durable-workflow-replay-verify = " durable_workflow.replay_verify:main"
61+ durable-workflow-history-bundle-verify = " durable_workflow.history_bundle_verify:main"
62+
5963[tool .setuptools .packages .find ]
6064where = [" src" ]
6165
Original file line number Diff line number Diff line change 138138from .replay_verify import (
139139 CaseReport as ReplayCaseReport ,
140140 GoldenHistoryReport ,
141+ SimulationReport ,
142+ aggregate_verdicts ,
143+ promotion_decision_for ,
144+ simulate_bundles ,
141145 verify_golden_history ,
142146 verify_replay ,
143147)
287291 "replay" ,
288292 "GoldenHistoryReport" ,
289293 "ReplayCaseReport" ,
294+ "SimulationReport" ,
295+ "aggregate_verdicts" ,
296+ "promotion_decision_for" ,
297+ "simulate_bundles" ,
290298 "verify_golden_history" ,
291299 "verify_replay" ,
292300 "HISTORY_BUNDLE_SCHEMA" ,
Original file line number Diff line number Diff line change @@ -661,6 +661,7 @@ def simulate_bundles(
661661 VERDICT_DRIFTED : 0 ,
662662 VERDICT_FAILED : 0 ,
663663 },
664+ missing_bundles = [str (directory )],
664665 error = f"Bundle directory [{ directory } ] does not exist." ,
665666 )
666667
@@ -674,6 +675,15 @@ def simulate_bundles(
674675 VERDICT_FAILED : 0 ,
675676 }
676677
678+ if not paths :
679+ return SimulationReport (
680+ verdict = VERDICT_FAILED ,
681+ promotion_decision = PROMOTION_BLOCK_AND_INVESTIGATE ,
682+ summary = summary ,
683+ missing_bundles = [str (directory / "*.json" )],
684+ error = f"No history-export bundle JSON files were found in [{ directory } ]." ,
685+ )
686+
677687 bundles : list [BundleEntry ] = []
678688 verdicts : list [str ] = []
679689
@@ -709,7 +719,7 @@ def simulate_bundles(
709719 summary ["total" ] += 1
710720 summary [verdict ] += 1
711721
712- overall = aggregate_verdicts (verdicts ) if verdicts else VERDICT_FAILED
722+ overall = aggregate_verdicts (verdicts )
713723
714724 return SimulationReport (
715725 verdict = overall ,
Original file line number Diff line number Diff line change @@ -428,6 +428,17 @@ def test_simulate_bundles_returns_failed_for_missing_directory(tmp_path: Path) -
428428
429429 assert report .verdict == VERDICT_FAILED
430430 assert report .promotion_decision == PROMOTION_BLOCK_AND_INVESTIGATE
431+ assert report .missing_bundles == [str (tmp_path / "no-such-dir" )]
432+ assert report .error is not None
433+
434+
435+ def test_simulate_bundles_returns_failed_when_directory_has_no_bundles (tmp_path : Path ) -> None :
436+ report = simulate_bundles (tmp_path )
437+
438+ assert report .verdict == VERDICT_FAILED
439+ assert report .promotion_decision == PROMOTION_BLOCK_AND_INVESTIGATE
440+ assert report .summary ["total" ] == 0
441+ assert report .missing_bundles == [str (tmp_path / "*.json" )]
431442 assert report .error is not None
432443
433444
You can’t perform that action at this time.
0 commit comments