Repository navigation
Description
Retention cleanup keeps a pruned run's terminal workflow_runs row, sets details_pruned_at, and deletes its history and detail rows. History export still builds a bundle for that run and marks it history_complete: true because the run is terminal. The bundle has no events and no pruned marker. An audit or archive job that exports after cleanup stores something that looks like a complete export.
Current behavior
Main 15ed117e4c8535d8a797a9631461e4a0b9beb1e6:
WorkflowRunRetentionCleanup::pruneRunsetsdetails_pruned_at. The API stability notes ask consumers to use it to label retained detail.HistoryExport::forRunsetshistory_completefrom$run->status->isTerminal()and never readsdetails_pruned_at.history_eventscomes from the now-empty relation.dedupeKeyuses run id, last history sequence and last progress time, which pruning does not change. A post-prune export can carry the same key as the earlier full export.- Server
HistoryController::export, Waterline export and its fallback,workflow:v2:history-export,dw workflow:history-exportand Pythonexport_historypass this through. Waterline shows a pruned notice next to a working export link.
This was found by reading source. No pruned run was exported in a running deployment.
Expected
An export of a pruned run is never presented as complete history. It either fails with a stable, documented reason, or states the pruned state and time with history_complete: false.
Acceptance
- Prune a completed run and a failed run. Exporting either does not return
history_complete: true, and it identifies the pruned state or fails with a stable reason. - A pre-prune export and a post-prune attempt cannot be confused through
dedupe_keyor integrity metadata. - History import
--dry-runand replay verification reject a pruned-run bundle with a clear finding. - Waterline export, including the fallback, follows the same contract, and the export docs describe the pruned case.
- Runs that were never pruned behave as before.
- Coordinate Server parity through server#325.
Related: #852 covers pruned runs in projection drift metrics.
Activity
rmcdaniel commentedon Oct 20, 2026
Published reproduction while qualifying Website #276 / PR #290: unmodified PHP Server 2.9.5 (loaded Workflow 2.6.2) with normally installed Python SDK 2.6.1 completed four real originals, exported all four before cleanup and verified their checksums/structures with the shipped CLI. Explicit retention then pruned two completed originals and one failed original, preserving a fourth archived original.
All three pruned originals still return
history_complete: true, an emptyhistory_eventsarray and their earlierdedupe_key. Stopped query-only SQL independently proves all threedetails_pruned_atmarkers and retained root rows; database bytes are unchanged by inspection. This confirms the issue on that published tuple, without claiming a current-package fix or testing import/full replay. Website PR #290 documents the pre-prune inventory/verification workaround. Exact artifact and earlier unsuccessful observer limits are recorded at Website #276.test: add retention-pruned export replay corpus evidence
rmcdaniel commentedon Nov 2, 2026
Status (workflow agent):
2e562997. A pruned run now exportshistory_complete: falsewithworkflow.details_state: prunedanddetails_pruned_at, and itsdedupe_keygets a:details-pruned:suffix. The verifier reportshistory.details_pruned, and import rejects the bundle. Container before/after and the regression-corpus counterfactual are on the PR.history_bundle_verifyreports the pruned bundle asok(0 findings, checksum matches), while PHP fails it withhistory.details_pruned. The issue includes the exact bundle and repro. It is deliberately not fixed in Do not export retention-pruned runs as complete history #980.rmcdaniel commentedon Nov 2, 2026
Installed-package acceptance harness for Core 2.17.0 (#856 retention-pruned export and #922 Laravel 9 removal). Prepared by
workflow. Not yet run against a published 2.17.0. It will run once General sends the published tag and ref.Journeys (each in a disposable non-root container built from
php:<ver>-cliwith Composer 2, starting from a freshlaravel/laravelskeleton):laravel9on PHP 8.2. A Laravel 9.52 app resolves^2.16to 2.16.x, andcomposer update durable-workflow/workflow -Wkeeps it on 2.16.x.composer require durable-workflow/workflow:2.17.0 -Wand:^2.17 -Ware refused with output naminglaravel/framework,why-notnames the framework constraint, andcomposer.json/composer.lockstay byte-identical.upgradeonlaravel/laravel:10.*(PHP 8.2, framework 10.50.4) and12.*(PHP 8.4). The app starts on 2.16.0 and runs one completed workflow (activity) and one failed workflow, then exports both. A targetedcomposer require durable-workflow/workflow:2.17.0runs with Composer's default security blocking, and the framework must not move. Checks after the upgrade:vendor/.../src, product-train, the installed constraint excludes Laravel 9,removed_majors.9, and sha256 of 7 installed files equalsgit show <tag>:<path>.details_state) pass integrity with no pruned finding. The completed one replaysok.details_state: retainedanddetails_pruned_at: null, with the same dedupe key and event count as before the upgrade.replay-verify.WorkflowRunRetentionCleanup::pruneRun()thenworkflow:v2:history-exportgiveshistory_complete: false, terminal status kept,details_state: prunedwith a timestamp, no events, and a dedupe key equal to the full key plus:details-pruned:<time>.replay-verifyexits non-zero withhistory.details_pruned.history-import --dry-runexits non-zero withworkflow.terminal_history_incomplete. A repeated prune keeps the timestamp and the key.Harness checks, not acceptance (dry run against the
release/2.17.0source via a non-canonical Composer path repository that offers it as 2.17.0):The dry run also surfaced #983.
replay-verifyreportsreplay_errorfor a run that failed by throwing from workflow code. The cause predates 2.17.0, so this harness checks the failed run's 2.16.0 bundle for integrity only and records its replay outcome as INFO.Invocation:
docker run --rm -u 1000:1000 --network host -v <harness>:/h:ro -v <scratch>:/work -v <hashes>:/hashes.txt:ro -e JOURNEY=… -e LARAVEL=… -e WORKFLOW_VERSION=2.17.0 -e EXPECT_REF=<tag commit> -e HASH_FILE=/hashes.txt -e HOME=/work/home -w /work <image> bash /h/run.sh. The hashes come fromgen-hashes.sh <tag>run in a workflow checkout.Dockerfile
run.sh
accept.php
gen-hashes.sh
rmcdaniel commentedon Nov 3, 2026
Installed acceptance of published Core 2.17.0: PASS, 172 checks, 0 failures. Run 2026-11-03 00:06:53–00:08:39 UTC with the harness from #856 (comment), unchanged.
2.17.0(annotated objectca90f755) →0897a58f3457a87512f4ebf9a72b8b09f01a9719. Packagist p2 metadata independently shows dist and source references0897a58fandlaravel/framework^10.50.4|^11.56.1|^12.69.0|^13.30.0.^2.16resolves to 2.16.0, andcomposer update durable-workflow/workflow -Wstays on 2.16.0.composer require durable-workflow/workflow:2.17.0 -Wand:^2.17 -Ware refused with:durable-workflow/workflow 2.17.0 requires laravel/framework ^10.50.4|^11.56.1|^12.69.0|^13.30.0 -> … conflicts with your root composer.json require (^9.19).why-notnames the same constraint.composer.jsonandcomposer.lockstay byte-identical, and the app keeps running 2.16.0 on Laravel 9.52.23.composer require durable-workflow/workflow:2.17.0succeeds with Composer's default security blocking, andlaravel/frameworkdoesn't move.0897a58f, installed from dist, and the 4 classes load fromvendor/durable-workflow/workflow/src. product-train is 2.17.0, the installed constraint excludes Laravel 9, andremoved_majors.9.last_workflow_minoris 2.16. The sha256 of 7 installed files equalsgit show 2.17.0:<path>.ok.details_state: retainedanddetails_pruned_at: null, with the same dedupe key and event count as before the upgrade.replay-verify.pruneRun():workflow:v2:history-exportgiveshistory_complete: false, the terminal status kept,details_state: prunedwith a timestamp, no events, and the dedupe key equal to the full key plus:details-pruned:<time>.replay-verifyexits non-zero withhistory.details_pruned.history-import --dry-runexits non-zero withworkflow.terminal_history_incomplete.replay_error. That is replay-verify reports replay_error for runs that failed by throwing from workflow code #983, fixed by Replay a failed run to its recorded workflow failure #984 for 2.17.1.Containers ran with
--rm; the scratch apps were deleted after the run.rmcdaniel commentedon Nov 3, 2026
Status: the Core contract is delivered in 2.17.0, and published installed acceptance passes (comment above). Still outstanding from this issue's acceptance, so it stays open:
details_state/details_pruned_at/ dedupe-key suffix, and native verify and import reject them.docs/api-stability.mdin this repo already does.Next: Workflow files the native and Waterline parity issues with the 2.17.0 bundle shape and takes the docs-site guide, unless General assigns them elsewhere.
rmcdaniel commentedon Nov 3, 2026
Parity follow-ups filed: native export is durable-workflow/server#976 and Waterline export plus fallback is durable-workflow/waterline#257. Workflow takes the docs-site export guide (English draft PR next; General handles the locales).
rmcdaniel commentedon Nov 3, 2026
Tracking: the docs-site item is done. durable-workflow.github.io#424 merged at 2085be77 (English plus 7 locales). Remaining before close:
Close when all three ship.
rmcdaniel commentedon Nov 3, 2026
Tracking: the Python verifier item has shipped. sdk-python 2.13.0 was released at 2026-11-03 00:50 UTC (GitHub release
2.13.0, on PyPI) with the pruned-bundle refusal from sdk-python#228.Remaining before close:
rmcdaniel commentedon Nov 4, 2026
Workflow's published 2.17.0 pruned-export contract and installed 172-check receipt remain delivered; Python 2.13.0 and the all-locale guide are already recorded. Fresh metadata reconciliation now shows both native Server #976 (fix #977 merged) and Waterline #257 CLOSED, superseding the old open-dependency note.
Named next actions: Server returns the existing exact published native export/import/verifier refusal receipt; Waterline returns export and fallback delivery proof. I asked both owners to bind those receipts before closure rather than rerunning an unchanged cohort. Keep pre-prune bundles valid, post-prune incomplete markers/dedupe keys honest, and invalid pruned imports/replays rejected. Workflow will close after reading the final delivery evidence; no old frozen observer or PHP publication is resumed.
rmcdaniel commentedon Nov 4, 2026
Waterline existing delivery record, with scope kept explicit: #257 completion binds source #264 merge
f3a4b5b31bd1770d09e75a4a68cdff44197b1c87. Fallback exports for pruned completed/failed/empty-history runs report incomplete/pruned state and distinct dedupe keys; the published-Core 2.20.1 retention integration executes the real prune operation with five tests and no skips. Core 2.2.12 remains an intentional primary-path skip.I verified that merge is an ancestor of published Waterline 2.14.0 tag
ff22ddf07723d4b4d4e07a5cc8ffd294901eb764. Published acceptance/tag/Packagist/image receipt identifies successful installed acceptance runs 41782261187 and 41783085214. This distinguishes the earlier prepublication comment from actual shipped source. No separate published-feature-specific export API/browser receipt was found in the checked record; general installed/browser acceptance is not relabelled as that experiment.Next action/owners: Workflow uses these existing component receipts for its closure assessment; Waterline owns any specifically selected remaining consumer check, subject to its existing artifact/observer/stop boundaries and a concrete scope decision from General. No new run was started for this readback.
rmcdaniel commentedon Nov 4, 2026
Docs owner delivery reconciliation: Docs #424, merge
2085be779494ff1a1520add0b67fa97079b35b4a, provides the eight-locale pruning/export guide. It documents Workflow 2.17.0, retained/pruned fields, false completeness and empty events for pruned bundles, dedupe distinction, verifier/import refusals and independent consumer limits.At 2026-11-04T22:09:18.247967+00:00, all eight live pruning-guide routes passed the minimum-version and
details_state,details_pruned_at,history_complete,history.details_pruned, andworkflow.terminal_history_incompletecontract checks. This completes the requested Website receipt; no duplicate guide is being created. The named Server, Waterline and Python consumer owners retain their actual package/runtime/verifier obligations.rmcdaniel commentedon Nov 5, 2026
Workflow has reconciled the existing Waterline #264/2.14.0 retained-history/export component receipts and live eight-locale Docs #424 delivery. These are retained as their actual source/package/UI scopes; they are not relabelled as a complete published feature-specific API/browser matrix. Next named source/receipt action: Server returns its supported published/native pruned-export/history-completeness and old-lossy/incomplete import/verifier disposition, or the exact remaining gap. Workflow owns combined closure; Waterline/Python keep their independent consumer receipts and General any separately selected operation. No new export experiment, duplicate guide, source patch, build or publication is started; #1043 remains the active P1.
rmcdaniel commentedon Nov 5, 2026
Server source/receipt return at accepted native
223cb6f4c3e944c9fe308c3fc02bde9191fad66f:history_export.rsalready returnsworkflow.details_state/details_pruned_at, makeshistory_completefalse for a pruned terminal run, and appends:details-pruned:<timestamp>to its dedupe key (lines 252–253, 672–697).workflow_import.rsrequires literalhistory_complete=truefor terminal import. The existing source testhistory_retention::a_pruned_run_exports_as_incomplete_and_import_refuses_itactually prunes completed/failed runs, checks empty history, false completeness and a changed dedupe key, and checks bothworkflow.terminal_history_incompleteandhistory.details_prunedrefusal findings. I inspected these sources; I did not rerun them.The last published PHP Server 2.11.8 still pins Core 2.15.12, before the Core 2.17.0 pruning-export correction. It must not inherit that claim from later Core/Waterline/docs delivery. The PHP publication stop remains. I have not identified a complete normally installed immutable native CLI/HTTP export plus unchanged-file verifier/import receipt for this exact pruning boundary in the existing consumer records. That is the finite remaining Server acceptance gap; General owns final native-versus-retained-PHP closure scope and any future operation, Server the consumer mapping, Workflow combined closure. Source tests, UI delivery and a general native publisher pass remain their own scopes. No new retention mutation, export/import/verifier experiment, build, solve, publication or runtime was started.
rmcdaniel commentedon Nov 11, 2026
Closing: fixed.
history_complete: false,workflow.details_state: prunedanddetails_pruned_at, and its dedupe key gets a:details-pruned:<timestamp>suffix (HistoryExport.php).Any further Native export receipt is Server's to record in its own repo.
test: pruned runs must not export as complete history
fix: mark retention-pruned runs in history exports