From fd21f58e57cf021a62ededbeb02aa4d573dcab4b Mon Sep 17 00:00:00 2001 From: memtomem <118508877+memtomem@users.noreply.github.com> Date: Mon, 9 Nov 2026 19:41:05 +0900 Subject: [PATCH 1/4] Freeze released workflows and read them with the current readers (#271) The Workflows family of the persisted-contracts D rows, part 1 of 2. tests/fixtures/persisted/workflows// holds ten workflows each from v0.2.0 (the first release with workflows) and v0.3.0, plus v0.3.0's document-created and revised plans. Each is its own project: the workflow database, the workflow directories (spec, planner output, node bundles, stream logs) and any workflow-origin run artifact. freeze.py writes them with the release's own CLI against fake codex and kimi that take their behaviour from a file under HOME (workflow agents run with an environment allowlist): - planned, completed, no-changes (gate off, empty final patch), gate-failed, failed, cancelled; - running (the durable job's worker SIGKILLed during the final node), recovery-required (the release's own status after that); - unapproved-completed (exit inside approve_workflow_run, after the completion commit) and unapproved-running (exit right after the final artifact is published, before it). The two exits, and a fixed host name so no real machine name is frozen in a lease owner, are hooks loaded into the release venv for the run only. No release wrote a v1 or v2 manifest: both normalize to v3 before the insert. The doc row said otherwise; it is corrected and names the synthetic tests that cover those readers. The test rebinds only the repository root, fingerprint and lease owner of each row and the final artifact's root and fingerprint, and asserts every other cell is unchanged. It reads the workflows through workflow list, show (--prompts), status, logs, lineage, export, cancel and prune, the events and node-bundle readers, recovery of a dead released lease (and a live owner kept with a stale heartbeat, clock fixed), and the final artifact through diff, runs show, apply (approved, after a prune, approval healed, refused for a workflow that never completed, no_changes) and the recover skip. Eight injected reader regressions each fail an intended assertion. Part 2 will cover a current run or resume of a released workflow, apply --verify environment narrowing and control-plan workflows. Co-authored-by: Claude Opus 5.5 --- docs/architecture/persisted-contracts.md | 2 +- tests/fixtures/persisted/workflows/freeze.py | 473 ++++++++++++ .../persisted/workflows/v0.2.0/bindings.json | 208 ++++++ .../workflows/.locks/wf-fa435cefbe524d19.lock | 0 .../wf-fa435cefbe524d19/planner-output.txt | 1 + .../workflows/wf-fa435cefbe524d19/spec.json | 25 + .../v0.2.0/cancelled/workflows/workflows.db | Bin 0 -> 24576 bytes .../v0.2.0/completed/runs/.apply.lock | 0 .../wf-55da1c5cde7b4e47-final/manifest.json | 41 ++ .../wf-55da1c5cde7b4e47-final/result.json | 7 + .../wf-55da1c5cde7b4e47-final/winner.patch | 8 + .../workflows/.locks/wf-55da1c5cde7b4e47.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 33 + .../winner.patch | 8 + .../wf-55da1c5cde7b4e47/nodes/fix/stream.log | 2 + .../wf-55da1c5cde7b4e47/planner-output.txt | 1 + .../workflows/wf-55da1c5cde7b4e47/spec.json | 25 + .../v0.2.0/completed/workflows/workflows.db | Bin 0 -> 24576 bytes .../workflows/.locks/wf-d99be4e0722d412f.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 19 + .../winner.patch | 0 .../wf-d99be4e0722d412f/nodes/fix/stream.log | 1 + .../wf-d99be4e0722d412f/planner-output.txt | 1 + .../workflows/wf-d99be4e0722d412f/spec.json | 25 + .../v0.2.0/failed/workflows/workflows.db | Bin 0 -> 24576 bytes .../workflows/.locks/wf-62a8b48d99e44279.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 33 + .../winner.patch | 8 + .../wf-62a8b48d99e44279/nodes/fix/stream.log | 2 + .../wf-62a8b48d99e44279/planner-output.txt | 1 + .../workflows/wf-62a8b48d99e44279/spec.json | 25 + .../v0.2.0/gate-failed/workflows/workflows.db | Bin 0 -> 24576 bytes .../v0.2.0/no-changes/runs/.apply.lock | 0 .../wf-cde618c312b34866-final/manifest.json | 39 + .../wf-cde618c312b34866-final/result.json | 7 + .../wf-cde618c312b34866-final/winner.patch | 0 .../workflows/.locks/wf-cde618c312b34866.lock | 0 .../commit.json | 1 + .../result.json | 25 + .../winner.patch | 0 .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 19 + .../winner.patch | 0 .../wf-cde618c312b34866/nodes/fix/stream.log | 2 + .../wf-cde618c312b34866/planner-output.txt | 1 + .../workflows/wf-cde618c312b34866/spec.json | 25 + .../v0.2.0/no-changes/workflows/workflows.db | Bin 0 -> 24576 bytes .../workflows/.locks/wf-5dae0f0d596348b5.lock | 0 .../wf-5dae0f0d596348b5/planner-output.txt | 1 + .../workflows/wf-5dae0f0d596348b5/spec.json | 25 + .../v0.2.0/planned/workflows/workflows.db | Bin 0 -> 24576 bytes .../workflows/.locks/wf-a5e80d4b34c1400b.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../wf-a5e80d4b34c1400b/planner-output.txt | 1 + .../workflows/wf-a5e80d4b34c1400b/spec.json | 25 + .../recovery-required/workflows/workflows.db | Bin 0 -> 24576 bytes .../workflows/.locks/wf-a5e80d4b34c1400b.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../wf-a5e80d4b34c1400b/planner-output.txt | 1 + .../workflows/wf-a5e80d4b34c1400b/spec.json | 25 + .../v0.2.0/running/workflows/workflows.db | Bin 0 -> 24576 bytes .../wf-10ba8b77464248b9-final/manifest.json | 35 + .../wf-10ba8b77464248b9-final/result.json | 7 + .../wf-10ba8b77464248b9-final/winner.patch | 8 + .../workflows/.locks/wf-10ba8b77464248b9.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 33 + .../winner.patch | 8 + .../wf-10ba8b77464248b9/nodes/fix/stream.log | 2 + .../wf-10ba8b77464248b9/planner-output.txt | 1 + .../workflows/wf-10ba8b77464248b9/spec.json | 25 + .../workflows/workflows.db | Bin 0 -> 24576 bytes .../wf-21ef6ff85c3a4fb9-final/manifest.json | 35 + .../wf-21ef6ff85c3a4fb9-final/result.json | 7 + .../wf-21ef6ff85c3a4fb9-final/winner.patch | 8 + .../workflows/.locks/wf-21ef6ff85c3a4fb9.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 33 + .../winner.patch | 8 + .../wf-21ef6ff85c3a4fb9/nodes/fix/stream.log | 2 + .../wf-21ef6ff85c3a4fb9/planner-output.txt | 1 + .../workflows/wf-21ef6ff85c3a4fb9/spec.json | 25 + .../unapproved-running/workflows/workflows.db | Bin 0 -> 24576 bytes .../persisted/workflows/v0.3.0/bindings.json | 242 ++++++ .../workflows/.locks/wf-8182839e1fba4058.lock | 0 .../wf-8182839e1fba4058/planner-output.txt | 1 + .../workflows/wf-8182839e1fba4058/spec.json | 25 + .../v0.3.0/cancelled/workflows/workflows.db | Bin 0 -> 36864 bytes .../v0.3.0/completed/runs/.apply.lock | 0 .../wf-7339a776c4604b99-final/manifest.json | 42 ++ .../wf-7339a776c4604b99-final/result.json | 7 + .../wf-7339a776c4604b99-final/winner.patch | 8 + .../workflows/.locks/wf-7339a776c4604b99.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 33 + .../winner.patch | 8 + .../wf-7339a776c4604b99/nodes/fix/stream.log | 2 + .../wf-7339a776c4604b99/planner-output.txt | 1 + .../workflows/wf-7339a776c4604b99/spec.json | 25 + .../v0.3.0/completed/workflows/workflows.db | Bin 0 -> 36864 bytes .../workflows/.locks/wf-715ce7479b854ad2.lock | 0 .../workflows/wf-715ce7479b854ad2/spec.json | 25 + .../v0.3.0/created/workflows/workflows.db | Bin 0 -> 36864 bytes .../workflows/.locks/wf-0a91dd8ccd0a4561.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 19 + .../winner.patch | 0 .../wf-0a91dd8ccd0a4561/nodes/fix/stream.log | 1 + .../wf-0a91dd8ccd0a4561/planner-output.txt | 1 + .../workflows/wf-0a91dd8ccd0a4561/spec.json | 25 + .../v0.3.0/failed/workflows/workflows.db | Bin 0 -> 36864 bytes .../workflows/.locks/wf-5e5335a66e3e4a5f.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 33 + .../winner.patch | 8 + .../wf-5e5335a66e3e4a5f/nodes/fix/stream.log | 2 + .../wf-5e5335a66e3e4a5f/planner-output.txt | 1 + .../workflows/wf-5e5335a66e3e4a5f/spec.json | 25 + .../v0.3.0/gate-failed/workflows/workflows.db | Bin 0 -> 36864 bytes .../v0.3.0/no-changes/runs/.apply.lock | 0 .../wf-e7d96a830ad54285-final/manifest.json | 40 + .../wf-e7d96a830ad54285-final/result.json | 7 + .../wf-e7d96a830ad54285-final/winner.patch | 0 .../workflows/.locks/wf-e7d96a830ad54285.lock | 0 .../commit.json | 1 + .../result.json | 25 + .../winner.patch | 0 .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 19 + .../winner.patch | 0 .../wf-e7d96a830ad54285/nodes/fix/stream.log | 2 + .../wf-e7d96a830ad54285/planner-output.txt | 1 + .../workflows/wf-e7d96a830ad54285/spec.json | 25 + .../v0.3.0/no-changes/workflows/workflows.db | Bin 0 -> 36864 bytes .../workflows/.locks/wf-dd2f5d4d4a694390.lock | 0 .../wf-dd2f5d4d4a694390/planner-output.txt | 1 + .../workflows/wf-dd2f5d4d4a694390/spec.json | 25 + .../v0.3.0/planned/workflows/workflows.db | Bin 0 -> 36864 bytes .../workflows/.locks/wf-f212e2feb55a4cb8.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../wf-f212e2feb55a4cb8/planner-output.txt | 1 + .../workflows/wf-f212e2feb55a4cb8/spec.json | 25 + .../recovery-required/workflows/workflows.db | Bin 0 -> 36864 bytes .../workflows/.locks/wf-22e223bfd8e74689.lock | 0 .../workflows/.locks/wf-715ce7479b854ad2.lock | 0 .../workflows/wf-22e223bfd8e74689/spec.json | 25 + .../workflows/wf-715ce7479b854ad2/spec.json | 25 + .../v0.3.0/revised/workflows/workflows.db | Bin 0 -> 36864 bytes .../workflows/.locks/wf-f212e2feb55a4cb8.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../wf-f212e2feb55a4cb8/planner-output.txt | 1 + .../workflows/wf-f212e2feb55a4cb8/spec.json | 25 + .../v0.3.0/running/workflows/workflows.db | Bin 0 -> 36864 bytes .../wf-17969765182e4464-final/manifest.json | 36 + .../wf-17969765182e4464-final/result.json | 7 + .../wf-17969765182e4464-final/winner.patch | 8 + .../workflows/.locks/wf-17969765182e4464.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 33 + .../winner.patch | 8 + .../wf-17969765182e4464/nodes/fix/stream.log | 2 + .../wf-17969765182e4464/planner-output.txt | 1 + .../workflows/wf-17969765182e4464/spec.json | 25 + .../workflows/workflows.db | Bin 0 -> 36864 bytes .../wf-2d9b5846bc1b4adf-final/manifest.json | 36 + .../wf-2d9b5846bc1b4adf-final/result.json | 7 + .../wf-2d9b5846bc1b4adf-final/winner.patch | 8 + .../workflows/.locks/wf-2d9b5846bc1b4adf.lock | 0 .../commit.json | 1 + .../result.json | 27 + .../winner.patch | 8 + .../nodes/draft/stream.log | 3 + .../commit.json | 1 + .../result.json | 33 + .../winner.patch | 8 + .../wf-2d9b5846bc1b4adf/nodes/fix/stream.log | 2 + .../wf-2d9b5846bc1b4adf/planner-output.txt | 1 + .../workflows/wf-2d9b5846bc1b4adf/spec.json | 25 + .../unapproved-running/workflows/workflows.db | Bin 0 -> 36864 bytes tests/test_frozen_workflows.py | 690 ++++++++++++++++++ 233 files changed, 3659 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/persisted/workflows/freeze.py create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/bindings.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/.locks/wf-fa435cefbe524d19.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/runs/.apply.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/manifest.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/.locks/wf-55da1c5cde7b4e47.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/.locks/wf-d99be4e0722d412f.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/.locks/wf-62a8b48d99e44279.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/.apply.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/manifest.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/.locks/wf-cde618c312b34866.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/.locks/wf-5dae0f0d596348b5.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/.locks/wf-a5e80d4b34c1400b.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/running/workflows/.locks/wf-a5e80d4b34c1400b.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/running/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/manifest.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/.locks/wf-10ba8b77464248b9.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/manifest.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/.locks/wf-21ef6ff85c3a4fb9.lock create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/bindings.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/.locks/wf-8182839e1fba4058.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/runs/.apply.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/manifest.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/.locks/wf-7339a776c4604b99.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/created/workflows/.locks/wf-715ce7479b854ad2.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/created/workflows/wf-715ce7479b854ad2/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/created/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/.locks/wf-0a91dd8ccd0a4561.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/.locks/wf-5e5335a66e3e4a5f.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/.apply.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/manifest.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/.locks/wf-e7d96a830ad54285.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/.locks/wf-dd2f5d4d4a694390.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/wf-dd2f5d4d4a694390/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/wf-dd2f5d4d4a694390/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/.locks/wf-f212e2feb55a4cb8.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-22e223bfd8e74689.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-715ce7479b854ad2.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-22e223bfd8e74689/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-715ce7479b854ad2/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/running/workflows/.locks/wf-f212e2feb55a4cb8.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/running/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/manifest.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/.locks/wf-17969765182e4464.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/workflows.db create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/manifest.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/.locks/wf-2d9b5846bc1b4adf.lock create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/commit.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/result.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/stream.log create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/planner-output.txt create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/spec.json create mode 100644 tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/workflows.db create mode 100644 tests/test_frozen_workflows.py diff --git a/docs/architecture/persisted-contracts.md b/docs/architecture/persisted-contracts.md index 9ff5938..25b4415 100644 --- a/docs/architecture/persisted-contracts.md +++ b/docs/architecture/persisted-contracts.md @@ -53,7 +53,7 @@ A golden over the current writer does not protect these. A reader can stop accep |---|---|---|---| | Artifacts | run `manifest.json`, `result.json`, `winner.patch`, apply receipts (`applications/*.json`), in every state a standalone `run` leaves them (room-, session- and workflow-origin artifacts are frozen with those families) | `runs list` / `show` / `prune`, `conversations list` / `show`, `diff`, `apply`, `recover` (`_bound_receipt`), `continue`, the TUI artifact view | `test_frozen_artifacts.py` (v0.1.2, v0.3.0) | | Rooms | the room database (`rooms.sqlite3`, with the WAL a killed process leaves): rooms, rounds, candidates, members, workspaces, events, `sessions_json`, `selection_json` v1, `provenance_json` v1/v2, `conversation_json` v1; candidate `manifest.json`, patch and output; room-bound run artifacts. No release wrote `conversation_json` v2 or judgment rows | `room list` / `rounds` / `show` / `candidates` / `diff` / `compare` / `export`, `select`, `apply`, `judge`, `recover`, `close`, `prune` `RoomStore.member_sessions` (and through these `RoomCandidateStore.load`, `_parse_sessions_json`, `selection_record_status`, `room_outcome_status`, `round_provenance_status`, `round_conversation_status`); the standalone `apply` / `recover` refusal and skip for room-bound artifacts; a HEAD `room round` on a released room (`branch-from` resuming the released sessions on the released base patch, `fresh`, a first round on a draft, and the `continue-own` refusal when the released workspaces are gone); `room attach` over the released member log paths (tmux faked); the `apply --verify` settings notices; raw export (`--include` preview and confirm); the TUI Rooms tab's readers and pure renders (`build_room_evidence`, `render_room`, `render_round_table`, and the Diff, Output and Details views), not the Textual app | `test_frozen_rooms.py` (v0.1.2, v0.2.0, v0.3.0) | -| Workflows | stored `spec_json`, manifest `schema_version` 1 and 3 with accounting, node `result.json` and `commit.json`; workflow-origin run artifacts (v0.2.0 on) | `WorkflowStore.load`, bundle verification; for workflow-origin artifacts, the approval check at `apply` | not yet | +| Workflows | the workflow database (`workflows.db`): rows with `spec_json`, manifest `schema_version` 3 with `calls_used`, events, and from v0.3.0 the lineage columns and `file`/`revision` origins; per workflow `spec.json`, planner output, node bundles (`result.json`, `winner.patch`, `commit.json` v3) and stream logs; workflow-origin run artifacts, approved or not, with `plan.final_agent` from v0.3.0. No release wrote manifest v1 or v2 (both normalize to v3 before the insert); those readers stay covered by `test_workflow_durable.py::test_marked_v1_migration_preserves_exact_count_and_control_fields` and `test_workflow.py::test_a_workflow_whose_call_accounting_is_ambiguous_is_refused` | `workflow list` / `show` (`--prompts`) / `status` / `logs` / `lineage` / `export` / `cancel` / `prune`, a dead release-written lease recovered by `show` and `status`, `WorkflowStore.events` and `read_node_bundle`; for the final artifact `diff`, `runs show`, `apply` (approved, after a prune, approval healed from the database, refused for a workflow that never completed, `no_changes`), the standalone `recover` skip. A final artifact naming another run, and an unreadable store: `test_cli.py::test_apply_refuses_an_artifact_the_named_workflow_did_not_finalize` and `test_apply_guard_fails_closed_when_the_workflow_store_cannot_be_read`. Not yet: a HEAD `run` or `resume` of a released workflow, `apply --verify` environment narrowing, control-plan (toolgraph) workflows, recovery of an interrupted workflow-origin apply | `test_frozen_workflows.py` (v0.2.0, v0.3.0), partly | | Sessions and project | isolated-session documents and their checkpoint run artifacts, project marker `.syncmill/project.json` | `IsolatedSessionStore.load` / `list`, `_marker_project_id`; the artifact readers for checkpoint runs | not yet | | Board | item rows and their evidence and inputs, decisions | `_row_to_item`, `_load_evidence`, `_row_to_decision` | not yet | | Memory backends | Mem0 and LangGraph stored payloads | the backends' read paths | not yet | diff --git a/tests/fixtures/persisted/workflows/freeze.py b/tests/fixtures/persisted/workflows/freeze.py new file mode 100644 index 0000000..8cd7f57 --- /dev/null +++ b/tests/fixtures/persisted/workflows/freeze.py @@ -0,0 +1,473 @@ +"""Produce the frozen workflow documents under this directory (#271). + +Run once per release, with THAT release's interpreter and source, never the +current one. The output directory must not exist; frozen fixtures are never +regenerated. + + git worktree add --detach /tmp/wt-v0.3.0 v0.3.0 + (cd /tmp/wt-v0.3.0 && uv sync --frozen --no-dev) + python tests/fixtures/persisted/workflows/freeze.py \\ + --release-root /tmp/wt-v0.3.0 --out tests/fixtures/persisted/workflows/v0.3.0 + +Each scenario is one project with its own workflow database. The released +CLI makes every scenario (`workflow plan`, `run`, `cancel`, `status`, and +v0.3.0's `create` and `revise`) against a fake `codex` (the planner and the +`draft` node) and a fake `kimi` (the final `fix` node). Workflow agents run +with an environment allowlist, so the fakes take their behaviour from a file +under HOME, which the allowlist keeps. + +`workflow run` launches a durable job whose worker owns the workflow lease. +Three states need that worker to die: +- `running`: SIGKILLed while the final node's agent works (`draft` is done). +- `unapproved-completed`: exits inside `approve_workflow_run`, after the + fenced completion commit and before the approval is written. +- `unapproved-running`: exits right after the final artifact is published, + before the completion commit. +The two exits are hooks loaded into the worker's own interpreter through a +`.pth` file this script puts in the release venv for the run and removes +afterwards; they act only while HOME holds a `crash-at` file. + +The project's state directory (`//`: the workflow +database, workflow directories and any workflow-origin run artifact) is +copied byte for byte once every writer has stopped, including any WAL a +killed process leaves. Workspaces and TUI job records are not copied. +`bindings.json` records the values bound to the generating machine. +""" + +from __future__ import annotations + +import argparse +import fcntl +import json +import os +import shutil +import signal +import sqlite3 +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +TREE = {"calc.py": "def add(a, b):\n return a - b\n"} +GATE = ["python3 -c \"from calc import add; assert add(2, 3) == 5\""] +SPEC = { + "schema_version": 1, + "goal": "fix add", + "final_node": "fix", + "nodes": [ + {"id": "draft", "agent": "codex", "prompt": "draft the fix"}, + {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}, + ], +} +REVISED = {**SPEC, "goal": "fix add, revised"} + +# HOME/fake-control.json maps the binary to one behaviour: fix (writes a + b), +# wrong (a * b, fails the gate), none (changes nothing), fail (exits 1), hang. +# The planner call (codex, prompt naming final_node) answers with the spec. +FAKE_AGENT = '''\ +import json, os, sys, time +from pathlib import Path +BINARY = os.path.basename(sys.argv[0]) +args = sys.argv[1:] +if args[:2] == ["login", "status"] or args[:1] in (["--version"], ["--help"]): + print(BINARY + " (fake) - logged in"); sys.exit(0) +home = Path(os.environ["HOME"]) +control = json.loads((home / "fake-control.json").read_text()) +if BINARY == "codex" and "final_node" in " ".join(args): + message = json.dumps(control["spec"]) +else: + plan = control.get(BINARY, "fix") + if plan == "hang": + marker = home / "started" / BINARY + marker.parent.mkdir(exist_ok=True) + marker.with_suffix(".tmp").write_text(str(os.getpid())) + os.replace(marker.with_suffix(".tmp"), marker) + time.sleep(300); sys.exit(1) + if plan == "fail": + sys.stderr.write(BINARY + " (fake): refused\\n"); sys.exit(1) + body = {"fix": "a + b", "wrong": "a * b"}.get(plan) + if body: + Path("calc.py").write_text("def add(a, b):\\n return " + body + "\\n", encoding="utf-8") + message = BINARY + " (fake) " + plan + "." +if BINARY == "codex": + events = [ + {"type": "thread.started", "thread_id": "fake-codex-thread"}, + {"type": "item.completed", "item": {"type": "agent_message", "text": message}}, + {"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}}, + ] +else: + events = [ + {"role": "assistant", "content": message}, + {"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", + "content": "To resume this session: kimi -r fake-kimi-session"}, + ] +for event in events: + print(json.dumps(event)) +''' + +# Loaded at interpreter start by the .pth below, in every process of the +# release venv. HOME/fake-host pins the host name the release records in a +# lease owner (so no real machine name is frozen); HOME/crash-at names a +# crash point. Without those files it does nothing. +CRASH_HOOK = '''\ +import os +from pathlib import Path + + +def _install(): + home = Path(os.environ.get("HOME", "/nonexistent")) + host = home / "fake-host" + if host.is_file(): + import socket + name = host.read_text().strip() + socket.gethostname = lambda: name + flag = home / "crash-at" + if not flag.is_file(): + return + point = flag.read_text().strip() + from syncmill.artifacts import ArtifactStore + + if point == "approve": + def approve_workflow_run(self, *args, **kwargs): + os._exit(0) + ArtifactStore.approve_workflow_run = approve_workflow_run + elif point == "publish": + original = ArtifactStore.save + + def save(self, *args, **kwargs): + saved = original(self, *args, **kwargs) + if str(kwargs.get("run_id", "")).endswith("-final"): + os._exit(0) + return saved + ArtifactStore.save = save + + +_install() +''' +HOOK_MODULE = "_syncmill_freeze_hook" +FROZEN_HOST = "frozen-host" + +GIT_ENV = { + "GIT_AUTHOR_NAME": "frozen", "GIT_AUTHOR_EMAIL": "frozen@example.invalid", + "GIT_COMMITTER_NAME": "frozen", "GIT_COMMITTER_EMAIL": "frozen@example.invalid", + "GIT_AUTHOR_DATE": "2026-01-01T00:00:00Z", "GIT_COMMITTER_DATE": "2026-01-01T00:00:00Z", +} + + +def _site_packages(python: Path) -> Path: + return Path(subprocess.run( + [str(python), "-c", "import sysconfig; print(sysconfig.get_paths()['purelib'])"], + check=True, capture_output=True, text=True, + ).stdout.strip()) + + +def _lock_free(path: Path) -> bool: + """Whether no process holds an flock on ``path`` (absent counts as free).""" + try: + handle = path.open("rb") + except FileNotFoundError: + return True + with handle: + try: + fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + return False + fcntl.flock(handle, fcntl.LOCK_UN) + return True + + +def _alive(pid: int) -> bool: + try: + os.kill(pid, 0) + except ProcessLookupError: + return False + except PermissionError: + return True + return True + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--release-root", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + release = args.release_root.resolve() + out = args.out.resolve() + if out.exists(): + sys.exit(f"{out} exists; frozen fixtures are never regenerated") + python = release / ".venv" / "bin" / "python" + syncmill = release / ".venv" / "bin" / "syncmill" + version = subprocess.run( + [str(python), "-c", "import importlib.metadata as m; print(m.version('syncmill'))"], + check=True, capture_output=True, text=True, + ).stdout.strip() + site = _site_packages(python) + hook_files = [site / f"{HOOK_MODULE}.py", site / f"zz{HOOK_MODULE}.pth"] + if any(path.exists() for path in hook_files): + sys.exit(f"{site} already holds the freeze hook") + + # The job manager's control socket lives in XDG_RUNTIME_DIR; a socket + # path may not exceed ~104 bytes, so it gets a short directory of its own. + runtime = Path(tempfile.mkdtemp(prefix="smwf.", dir="/tmp")) + try: + hook_files[0].write_text(CRASH_HOOK, encoding="utf-8") + hook_files[1].write_text(f"import {HOOK_MODULE}\n", encoding="utf-8") + with tempfile.TemporaryDirectory() as tmp: + _generate(Path(tmp).resolve(), runtime, syncmill, version, out) + finally: + for path in hook_files: + path.unlink(missing_ok=True) + shutil.rmtree(runtime, ignore_errors=True) + + +def _generate(box: Path, runtime: Path, syncmill: Path, version: str, out: Path) -> None: + repo, home, state, fakebin = (box / n for n in ("repo", "home", "state", "fakebin")) + for folder in (repo, home, state, fakebin): + folder.mkdir() + (home / "fake-host").write_text(FROZEN_HOST) + for binary in ("codex", "kimi"): + (fakebin / binary).write_text(f"#!{sys.executable}\n" + FAKE_AGENT, encoding="utf-8") + (fakebin / binary).chmod(0o755) + (fakebin / "python3").symlink_to(sys.executable) + base_env = { + "HOME": str(home), + "PATH": os.pathsep.join([str(fakebin), "/usr/bin", "/bin"]), + "TMPDIR": str(box), + "XDG_RUNTIME_DIR": str(runtime), + "GIT_CONFIG_NOSYSTEM": "1", + "PYTHONDONTWRITEBYTECODE": "1", + "SYNCMILL_AGENT_PRIORITY": json.dumps(["codex", "kimi-code"]), + "SYNCMILL_CLAUDE__ENABLED": "false", + "SYNCMILL_RUNS__ROOT": str(state / "runs"), + "SYNCMILL_WORKSPACE__ROOT": str(state / "workspaces"), + "SYNCMILL_MEMORY__DB_PATH": str(state / "memory" / "memtomem.db"), + "SYNCMILL_MEMORY__MEMORY_DIR": str(state / "memory" / "files"), + "SYNCMILL_BREAKER__STATE_PATH": str(state / "breaker.json"), + "SYNCMILL_TUI__STATE_DIR": str(state / "tui"), + **GIT_ENV, + } + + def git(*argv: str) -> None: + subprocess.run(["git", *argv], cwd=repo, env=base_env, check=True, + capture_output=True) + + git("init", "-q", "-b", "main") + for name, text in TREE.items(): + (repo / name).write_text(text, encoding="utf-8") + git("add", "-A") + git("commit", "-q", "-m", "frozen tree") + + out.mkdir(parents=True) + bindings: dict = { + "release": version, + "box_root": str(box), + "hostname": FROZEN_HOST, + "tree": TREE, + "spec": SPEC, + "scenarios": {}, + } + + class Scenario: + def __init__(self, name: str, *, gate: bool = True, **control: str): + self.name = name + self.project = f"frozen-workflows-{name}" + self.env = {**base_env, "SYNCMILL_PROJECT__ID": self.project} + if gate: + self.env["SYNCMILL_GATE__COMMANDS"] = json.dumps(GATE) + self.control = {"spec": SPEC, **control} + self.workflow_ids: list[str] = [] + + @property + def project_dir(self) -> Path: + return state / "runs" / self.project + + def prepare(self, crash_at: str | None = None) -> None: + (home / "fake-control.json").write_text(json.dumps(self.control)) + flag = home / "crash-at" + flag.unlink(missing_ok=True) + if crash_at: + flag.write_text(crash_at) + shutil.rmtree(home / "started", ignore_errors=True) + + def workflow(self, *argv: str, ok: bool = True) -> str: + done = subprocess.run( + [str(syncmill), "workflow", *argv], cwd=repo, env=self.env, + capture_output=True, text=True, + ) + if ok and done.returncode != 0: + sys.exit(f"{self.name}: workflow {argv}: {done.stdout}{done.stderr}") + return done.stdout + done.stderr + + def newest_workflow(self) -> str: + listing = self.workflow("list") + ids = [line.split()[0] for line in listing.splitlines() + if line.startswith("wf-")] + new = [wf for wf in ids if wf not in self.workflow_ids] + if len(new) != 1: + sys.exit(f"{self.name}: expected one new workflow, found {new}") + self.workflow_ids.append(new[0]) + return new[0] + + def plan(self) -> str: + self.prepare() + self.workflow("plan", "fix add", "--planner", "codex") + return self.newest_workflow() + + def settle(sc: Scenario, timeout: float = 60) -> None: + """Wait until no process holds the workflow or job locks.""" + locks = [*sc.project_dir.glob("workflows/.locks/*.lock"), + *(state / "tui").glob("*/locks/*.lock")] + deadline = time.monotonic() + timeout + while not all(_lock_free(lock) for lock in locks): + if time.monotonic() > deadline: + sys.exit(f"{sc.name}: a writer still holds a lock") + time.sleep(0.2) + + def freeze(sc: Scenario) -> None: + settle(sc) + source = sc.project_dir + target = out / sc.name + shutil.copytree(source, target) + # Nothing that a digest covers may carry this machine's paths; only + # the named binding locations may. + for path in target.rglob("*"): + if path.is_file() and path.suffix in {".json", ".patch", ".txt", ".log"}: + if str(box).encode() in path.read_bytes() and path.name != "manifest.json" \ + and "applications" not in path.parts: + sys.exit(f"{sc.name}: {path} carries the generating path") + # Bindings from a scratch copy, so a crash's WAL is seen and the + # frozen files are never opened. + scratch = box / "bindings-read" + shutil.rmtree(scratch, ignore_errors=True) + shutil.copytree(source / "workflows", scratch) + with sqlite3.connect(scratch / "workflows.db") as conn: + conn.row_factory = sqlite3.Row + rows = [dict(row) for row in conn.execute( + "SELECT * FROM workflows ORDER BY created_at, workflow_id" + )] + conn.close() + bindings["scenarios"][sc.name] = { + "project_id": sc.project, + "workflow_ids": [row["workflow_id"] for row in rows], + "states": {row["workflow_id"]: row["state"] for row in rows}, + "source_fingerprints": sorted({row["source_fingerprint"] for row in rows}), + "lease_owners": sorted({row["lease_owner"] for row in rows if row.get("lease_owner")}), + "final_run_ids": sorted({row["final_run_id"] for row in rows if row["final_run_id"]}), + "run_artifacts": sorted(p.name for p in (source / "runs").glob("*/") + if p.is_dir()) if (source / "runs").is_dir() else [], + "wal": sorted(str(p.relative_to(source)) for p in source.rglob("*-wal")), + } + + def job_state(workflow_id: str) -> dict: + for spec in (state / "tui").glob("*/jobs/*/spec.json"): + if workflow_id in spec.read_text(): + state_file = spec.with_name("state.json") + if state_file.is_file(): + return json.loads(state_file.read_text()) + return {} + + def run_until(sc: Scenario, workflow_id: str, marker: str) -> None: + """Start a detached run, wait for an agent's start marker, then kill + the job's worker group and the agent's own group.""" + sc.workflow("run", workflow_id, "--detach") + started = home / "started" / marker + deadline = time.monotonic() + 120 + while not started.exists(): + if time.monotonic() > deadline: + sys.exit(f"{sc.name}: the {marker} agent never started") + time.sleep(0.1) + job = job_state(workflow_id) + if not job.get("pgid"): + sys.exit(f"{sc.name}: no job record for {workflow_id}") + os.killpg(int(job["pgid"]), signal.SIGKILL) + agent = int(started.read_text()) + try: + os.killpg(os.getpgid(agent), signal.SIGKILL) + except ProcessLookupError: + pass + deadline = time.monotonic() + 60 + while _alive(int(job["pid"])) or _alive(agent): + if time.monotonic() > deadline: + sys.exit(f"{sc.name}: the worker or its agent survived SIGKILL") + time.sleep(0.1) + + # ── the lifecycle ──────────────────────────────────────────────────── + sc = Scenario("planned") + sc.plan() + freeze(sc) + + sc = Scenario("completed") + workflow_id = sc.plan() + sc.workflow("run", workflow_id) + freeze(sc) + + # No gate, and neither node changes anything: an empty final patch. + sc = Scenario("no-changes", gate=False, codex="none", kimi="none") + workflow_id = sc.plan() + sc.workflow("run", workflow_id) + freeze(sc) + + # The final node's change fails the gate: a failing report, no artifact. + sc = Scenario("gate-failed", kimi="wrong") + workflow_id = sc.plan() + sc.workflow("run", workflow_id, ok=False) + freeze(sc) + + # The final node's only attempt fails. + sc = Scenario("failed", kimi="fail") + workflow_id = sc.plan() + sc.workflow("run", workflow_id, ok=False) + freeze(sc) + + sc = Scenario("cancelled") + workflow_id = sc.plan() + sc.workflow("cancel", workflow_id) + freeze(sc) + + # The worker dies while the final node's agent works; then the + # release's own `status` recovers the dead lease. + sc = Scenario("running", kimi="hang") + workflow_id = sc.plan() + sc.prepare() + run_until(sc, workflow_id, "kimi") + freeze(sc) + sc.name = "recovery-required" + sc.workflow("status", workflow_id) + freeze(sc) + + sc = Scenario("unapproved-completed") + workflow_id = sc.plan() + sc.prepare(crash_at="approve") + sc.workflow("run", workflow_id, ok=False) + (home / "crash-at").unlink() + freeze(sc) + + sc = Scenario("unapproved-running") + workflow_id = sc.plan() + sc.prepare(crash_at="publish") + sc.workflow("run", workflow_id, ok=False) + (home / "crash-at").unlink() + freeze(sc) + + if version != "0.2.0": + # A plan from a document, then a revision of it: one lineage, two + # plan versions, no planner call. + sc = Scenario("created") + document = box / "spec.json" + document.write_text(json.dumps(SPEC)) + sc.prepare() + sc.workflow("create", "--file", str(document)) + parent = sc.newest_workflow() + freeze(sc) + sc.name = "revised" + document.write_text(json.dumps(REVISED)) + sc.workflow("revise", parent, "--file", str(document)) + sc.newest_workflow() + freeze(sc) + + (out / "bindings.json").write_text(json.dumps(bindings, indent=2) + "\n") + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bindings.json b/tests/fixtures/persisted/workflows/v0.2.0/bindings.json new file mode 100644 index 0000000..c4be9b1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bindings.json @@ -0,0 +1,208 @@ +{ + "release": "0.2.0", + "box_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpis4bisz9", + "hostname": "frozen-host", + "tree": { + "calc.py": "def add(a, b):\n return a - b\n" + }, + "spec": { + "schema_version": 1, + "goal": "fix add", + "final_node": "fix", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix" + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ] + } + ] + }, + "scenarios": { + "planned": { + "project_id": "frozen-workflows-planned", + "workflow_ids": [ + "wf-5dae0f0d596348b5" + ], + "states": { + "wf-5dae0f0d596348b5": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "completed": { + "project_id": "frozen-workflows-completed", + "workflow_ids": [ + "wf-55da1c5cde7b4e47" + ], + "states": { + "wf-55da1c5cde7b4e47": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-55da1c5cde7b4e47-final" + ], + "run_artifacts": [ + "wf-55da1c5cde7b4e47-final" + ], + "wal": [] + }, + "no-changes": { + "project_id": "frozen-workflows-no-changes", + "workflow_ids": [ + "wf-cde618c312b34866" + ], + "states": { + "wf-cde618c312b34866": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-cde618c312b34866-final" + ], + "run_artifacts": [ + "wf-cde618c312b34866-final" + ], + "wal": [] + }, + "gate-failed": { + "project_id": "frozen-workflows-gate-failed", + "workflow_ids": [ + "wf-62a8b48d99e44279" + ], + "states": { + "wf-62a8b48d99e44279": "failed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "failed": { + "project_id": "frozen-workflows-failed", + "workflow_ids": [ + "wf-d99be4e0722d412f" + ], + "states": { + "wf-d99be4e0722d412f": "failed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "cancelled": { + "project_id": "frozen-workflows-cancelled", + "workflow_ids": [ + "wf-fa435cefbe524d19" + ], + "states": { + "wf-fa435cefbe524d19": "cancelled" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "running": { + "project_id": "frozen-workflows-running", + "workflow_ids": [ + "wf-a5e80d4b34c1400b" + ], + "states": { + "wf-a5e80d4b34c1400b": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "46320@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "recovery-required": { + "project_id": "frozen-workflows-running", + "workflow_ids": [ + "wf-a5e80d4b34c1400b" + ], + "states": { + "wf-a5e80d4b34c1400b": "recovery_required" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "unapproved-completed": { + "project_id": "frozen-workflows-unapproved-completed", + "workflow_ids": [ + "wf-10ba8b77464248b9" + ], + "states": { + "wf-10ba8b77464248b9": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-10ba8b77464248b9-final" + ], + "run_artifacts": [ + "wf-10ba8b77464248b9-final" + ], + "wal": [] + }, + "unapproved-running": { + "project_id": "frozen-workflows-unapproved-running", + "workflow_ids": [ + "wf-21ef6ff85c3a4fb9" + ], + "states": { + "wf-21ef6ff85c3a4fb9": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "46553@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [ + "wf-21ef6ff85c3a4fb9-final" + ], + "wal": [] + } + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/.locks/wf-fa435cefbe524d19.lock b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/.locks/wf-fa435cefbe524d19.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..fb3aa935820944dec12e08d2c3c18071ef20ac6b GIT binary patch literal 24576 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 IpF&*v4+lWy_y7O^ literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/.locks/wf-d99be4e0722d412f.lock b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/.locks/wf-d99be4e0722d412f.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json new file mode 100644 index 0000000..fdd8a05 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-d99be4e0722d412f", "node_id": "draft", "lease_id": "1e2b8ee9522b4f899ec4381d5e68ca65", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "5bf65992498c42ace5753528d20bf207fec84b7e5132bbc8977782546c76b43f"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json new file mode 100644 index 0000000..ff2e2d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-d99be4e0722d412f", + "node_id": "draft", + "lease_id": "1e2b8ee9522b4f899ec4381d5e68ca65", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json new file mode 100644 index 0000000..4d95bc3 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-d99be4e0722d412f", "node_id": "fix", "lease_id": "1e2b8ee9522b4f899ec4381d5e68ca65", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "73455018bde6a139f65a690d9a62b4eeb0388a9cfd68c3790ae6ed02ee5227f0"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json new file mode 100644 index 0000000..8f6a2a0 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json @@ -0,0 +1,19 @@ +{ + "status": "failed", + "agent_id": "kimi-code", + "output": "", + "error": "kimi (fake): refused", + "exit_code": 1, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-d99be4e0722d412f", + "node_id": "fix", + "lease_id": "1e2b8ee9522b4f899ec4381d5e68ca65", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/stream.log new file mode 100644 index 0000000..899289d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/stream.log @@ -0,0 +1 @@ +kimi (fake): refused diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..b79a436c401cc114bd0937b74effc93dd891cc93 GIT binary patch literal 24576 zcmeI4&2QVt6~HMww&MkCbx|PRq6ZJyi(ShWzdwAcYjcYrwzIVrAi*LahMb|rHAQMk z${%YOMcT#o)I$&b2YTE?4?XuC)m4WtkE$E^w>I+qzOOUF)>rFG zOJ56fi{A)~-_-xQlo=oNbAylRZC z;inYml(?{@EGis6Z=u5%`}^Zd_qt??(pe6LClQ;{ZsMj%GLa5h(o22n24Q?c({38Z z6V>WQIF4!R;uAVqg{(_`_p>aCC+R>3$6-LTeBuhAJ&xS87gr;7XK)%QqBx^fegowc zcM4sqoa7ujbR(rrahiKDpk-=G&%!K(E<8PKIlpj@P&K+}&-r-g#eNG(l`z0UQ=C6k z__M?tYD6d|Q5aKZJYc2!4dmk}a{Z(Sh~L&^7E#oa%#$2|`U#9d zFC&FIV|u&p&n~PqS8H$P!kEyD%3U(Ibb~>{qtfl39_~3Whi+GuL(dO~iEAT1wC5b} zuGjx7{E0dBZ-4bZIQ1uU^D7%0wO56%+2;{#GTn@R-lMTkZ{5z{2u8Ou*J9kE4XC;P z>yI8SFp>#gJwdq&c<@j*>@nw?(Q7#?(8{@kpaCK6pJ3X`DOQZSc?-f zbk#1tj1o-71cP;XW`4z4tqFzsl>l)sPl|iDQY&-i!Pi-sS01yuWeNUjb3YZ}!!J&N z6W|0mfw2Vsa9sPbdT{WgH>+=!R=#}x#}`GZ+M=*ftJNN#2b;vUJ*rX3R1~7gN}x!J zu_?=&lHHP}ZQ0(|)h$(qYRx|H(!%XwS>u$gQ;`LoXNNpgoSoddbtmuZgjYQi7E6|8aXn?gql?^kX z0r3^A`j)JNY3RhTR9~m6?Rl1Fsj5K&-Ii@%RyD;^MG#p+Mpvcc+wiT}8?gp_ARAHG zg0YfAP)KICY_rlY1S84{&@@d_NGaHYvNydEHR^X37KrhcIiuwTh}) zh9nQGl#B2#&fBb&qBqULQkpVVJd09WR}@bREZe5Ordl%5sbTrp&^Ki;=&TpzTWlxA z+6O6q+x?ER&O^3`+bU%AI}=e@ySFiH1;KpLry^`X{H^Y#_?pr4^YDyqP733Ll_Asq zVU3w!uNRXDmcauv(82d~+g3E&@-+qfR5x{1S1h7P9)u4t3g6N^lj^dnc%E;8*_f81 zgCCoQr>Vh+(1AY|=GMz#klhq~CM885^0Y^bTdiH4RUvQ)Gd z;8VK3jZzx)vc9c7Y&)u{n3^>-gh_12)>XTVnoQNPwgayJjx1+FNe|l1yEYe7)pT8w zEsszG%c>2=gAH3EHa5UfAd*X}1@YPsAb9wyX-k+Ilt>Dt;7Mj6O=&L7ULGD!$uy;E z#$pXjG@Pbw7()vf+3_=H8VxvdF>h*HMJpo~P(>_=8wk!jVs^}hiWmaM*ui0pV8%*P zHleU15tf5)#6)(ckin+D@D3KC3zsQ;WAq#W5|p9A_0JZ{w3}xGkSJ|QeHR-Z!a!;n zQz4>;0mKfs3ejacy`X-N9gx5@hG4?1c4%c_8MCZlkO#E}#V$5&Ro4~6)`_P|nr>n2 zt5o-F%*wN%#U^Vc0xXumwCJ%W#bE%*@}vtf1GAWVGlLyk4Wrh(n7m*z&(kDw*#VkM z!V@^RQI>PPxbyeFoId&TKmVManfU-#v91UPJk2O1ULas zfD_;Z-gg2|X4S1UPJk2O1ULas;QLA7 zC$k5OqYu-JdkB?1|G&7jC)EF3C-o=Ge_Q@yxv}(*rQbsmzc>L-fD_;ZH~~(86W|0m z0ZxDu-~?_Fc(%A8s0SOuhhzSK_3r+w|B|0xJ^)@l@4wZ54*%-<+2iSiskS94vWEDW$>NZd&=O+<;sEr$z{jLTy@U@K(re-3HQ%_y6nv w7U07#PJk2O1ULasfD_;ZH~~(86W|0m0ZxDuc>f9fur|91tpET3 literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/.locks/wf-62a8b48d99e44279.lock b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/.locks/wf-62a8b48d99e44279.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json new file mode 100644 index 0000000..8706698 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-62a8b48d99e44279", "node_id": "draft", "lease_id": "bc5e7b6d629248399c44c60a4964a5c3", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "cf2164b8d489b4fb97731fd6995909e190587b501ad04a5b080a06cf58f4cddb"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/result.json new file mode 100644 index 0000000..35d35f9 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-62a8b48d99e44279", + "node_id": "draft", + "lease_id": "bc5e7b6d629248399c44c60a4964a5c3", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json new file mode 100644 index 0000000..99405ac --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-62a8b48d99e44279", "node_id": "fix", "lease_id": "bc5e7b6d629248399c44c60a4964a5c3", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "73504d91039ea4579d212c81b1e0799ecefd7612a13c4e07b52c76e6ceaa3d90"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/result.json new file mode 100644 index 0000000..ccaffae --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/result.json @@ -0,0 +1,33 @@ +{ + "status": "failed", + "agent_id": "kimi-code", + "output": "kimi (fake) wrong.", + "error": "quality gate failed: 0/1 checks passed; failed: python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": { + "passed": false, + "score": 0.0, + "checks": [ + { + "command": "python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "passed": false, + "exit_code": 1, + "output": "Traceback (most recent call last):\n File \"\", line 1, in \n from calc import add; assert add(2, 3) == 5\n ^^^^^^^^^^^^^^\nAssertionError", + "duration_seconds": 0.02 + } + ] + }, + "patch_sha256": "93a267aca73ce9b58f03c7393a75925def1745c1921d6862043aecb70be4196d", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-62a8b48d99e44279", + "node_id": "fix", + "lease_id": "bc5e7b6d629248399c44c60a4964a5c3", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch new file mode 100644 index 0000000..b78cd53 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..7f32081 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a * b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/stream.log new file mode 100644 index 0000000..f930192 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) wrong."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..0db2420159f4835e25fef53d0bb7bee94e2d7562 GIT binary patch literal 24576 zcmeI4&2Qtz6~HBbtk(f-HwYTH=)nW_V#Bhq$YJpuO3t~7kv=~z) zm!$j=!zi{bl2Z@8^e^ZshaP(FwLpQMb18z}i~a*W_6l{S2X6tu_PSd+@EU6}XwJ9^h|%SPncDVO z8%s;y)aDnztu21L^4BG2`J3gZcO@6$f`A|(2nYg#fFK|U2m;@Wz*pV+!s^;u{dXHV z_Cj_Zr>A}xpJ#)cnZ09Yx9ykYov<`X zgJ_~$Nre0^ z&TzM=OBEBJLxXOFbQn%^4?46AP1#wH1<-^CM{Va9&M~Tb7wtQr>^?bYBe@a=SXhel zHzoce_J%d04AU@(7*`(9(t`#fI1F79_W<$hiVUKpm%Dj<%A)MG3FBTqVahzt0Vs)~ z4|*9bsxzjw8)SB2wYgS*IUhunT~xy*bIV~c2$(B}-Gifj=jm|RRq4>9qha9s$Qasp z9`9|e{HOLOKB&L`)w?jLKb^m~y17|@R@60n5yB$V&De_`iwL{*c<+WWx)r$|;Vx@H z&W&IHXl{Xv{LRUY_Pgv1#@@iRm5=i`WL6zeMxu3)H_(s~V}^F0v>!b$QM)nd zj7m9&eShqH;v73id(PuQqHKLoOh7kyU+y~x4j9?J-N$>o`%Yt|LvI?zq6Kb#ncOyO zQA~$JHNlr*jOiF-u%XP{TXojzwL<+$fVh{(#j{&gD|3~>?JUeI_xaqi1mF7H-)ry@ zmmnYr2mNK7%@_Hzg>+*o)nB*Al%#@!^Z5}>>T z8^yr|&(9d;`JXqWJm@lTs9%OL# zEuZ<6s8}PGVt{HG)U-5WFwOQnOSd%5q`qM*Hc>QPwKNGtmXOg^S@9jXD;6)j00Lw~ z25TgqatI13{E@FF`iY=Nc>~`@GS+R-U4v*_3Mh2e3-fKh z)ZxNF3hB7tQ`ULF7g^hdjDBN62F`T{!&(j07kwtd3W01Vm(p`63X?oI_dQSxP4TH|+lDRMOtEFdYI$H2FqNU@o@~ij zHi>UozD_9hMuZN=W1()n3_96OVa%koXhWX%*fmsvkB=LQ(l;_(&|O+GGKhwnBE9Hg zaJnzi312Puh3p`?sUR>9YwwM<(RwDt}Thhk~CH?QTv85TZ zPHjcjY=(8CWm8pA!5(=ElUp#U5in6L6DA2oBRT|mhDur{GYP|3qqaPyHga`*bH6Hz z3D$7Rel=xn#L|qc`1x2JwPy;SmSNiErD)2ql*%B2SwgVi5%FyokZX|aV$1M7i%|?-jbZBw zwJqotjcF!N&ut?21&{==cmS_Oj~6Ko?Ms%&3790zP4>+QzG*cyif?0X1YaRfzVC&z#j}(7?SwnkmEnQsH1EIE8Lx;M*X*3@UAecbRhs2Pv{{ z87-6dOOi4_3{E;D1?4Fw;24zia-RncGOkQinoRv=OI4>dYcO4Cy7{(r`T9AuZ4@W8 z=eBOS1EM_UdU5w3e>wf(*WIPrnVI*%Nv^N_s|Fu&2?BzEARq_`0)l`bAP5Kof`A|( z2nYg#z`IW1!&z-{%x+SD{r_EU<-2#ifujC`fFK|U2m*qDARq_`0)l`bAP5Kog24YL zfuGJEE{?uTGwvN%{{H{s(td5_&ntB0!{xs%f4SUP`sdQ`AxK<;fFK|U2m*qDARq_` z0)l`bAP5Kow+K93T&QV>o3;1H{D1Y8^;iE4Asi#>zW`pn@4wxD5C7`=;r;1^!I>*X zmhTXDu)u|FOdG6QS>{d%}ggvwlq^I4+so;q5o4O6nddPtu)n;%hNWmN-NI) yul&0PA8`o+f`A|(2nYg#fFK|U2m*qDARq_`0)oK1PvC?4>}I{%0Vsb8aq+(+P6vMg literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/manifest.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/manifest.json new file mode 100644 index 0000000..4716541 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/manifest.json @@ -0,0 +1,39 @@ +{ + "schema_version": 1, + "run_id": "wf-cde618c312b34866-final", + "project_id": "frozen-workflows-no-changes", + "parent_run_id": null, + "created_at": "2026-11-09T10:19:46.677959+00:00", + "updated_at": "2026-11-09T10:19:46.687985+00:00", + "state": "no_changes", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpis4bisz9/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "4513f9a69dcb1d2a84110be7014f94f772809bb6", + "task_id": "43f96150-8f6f-4635-8951-76bd591a2041", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-cde618c312b34866", + "node_id": "fix", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "d7b327555dd235ee9693176d3e000b172284eda6e4042a1bd7c8f763b5af02cb", + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "approved": { + "workflow_id": "wf-cde618c312b34866", + "run_id": "wf-cde618c312b34866-final", + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "approved_at": "2026-11-09T10:19:46.687980+00:00" + } + }, + "agent_id": "kimi-code", + "files_changed": [], + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "provider_session_id": null, + "conversation_id": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/result.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/result.json new file mode 100644 index 0000000..abe0019 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/result.json @@ -0,0 +1,7 @@ +{ + "status": "completed", + "output": "kimi (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02 +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/.locks/wf-cde618c312b34866.lock b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/.locks/wf-cde618c312b34866.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json new file mode 100644 index 0000000..4987ba0 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-cde618c312b34866", "node_id": "draft", "lease_id": "2d1000d7a24f4a358e0f8efe65beb782", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "cbf6f4d323e39f30ec0b1ba490ba0892e85f6133b97267bfccb7600969498eff"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/result.json new file mode 100644 index 0000000..17cb5d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/result.json @@ -0,0 +1,25 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-cde618c312b34866", + "node_id": "draft", + "lease_id": "2d1000d7a24f4a358e0f8efe65beb782", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/stream.log new file mode 100644 index 0000000..88a7e4c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) none."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json new file mode 100644 index 0000000..23b44cc --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-cde618c312b34866", "node_id": "fix", "lease_id": "2d1000d7a24f4a358e0f8efe65beb782", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "d7b327555dd235ee9693176d3e000b172284eda6e4042a1bd7c8f763b5af02cb"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/result.json new file mode 100644 index 0000000..b4d22a3 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/result.json @@ -0,0 +1,19 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-cde618c312b34866", + "node_id": "fix", + "lease_id": "2d1000d7a24f4a358e0f8efe65beb782", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/stream.log new file mode 100644 index 0000000..f07794d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) none."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..dd9829d10f69221f413ac0e9e2b4161a8a58f4be GIT binary patch literal 24576 zcmeI4&2QVt6~HMww&MkCchSXqfjyXD54925B02o@p{;9ki=ei$brc{$u^@(=p~W>t zYDvm=)-Z~+#rD)53iJ=?v3uyD=Uxl+AJ|K==)LGa&|}|FBqiBOA_YN#phHM7IUjG{ z{N~M@4|DYRXUAbq#UM$0I2V!d9bsWX_>m|Ig0KMJW%yRt2Hf1M9^h|d?EYo93&P%4 zJ8NrS3rnlt2&><0{B6xy|9buD%aZeOPJk2O1ULasfD_;ZIDt1J@Kvv|vbnX@`0Y-P zy@*~W>3I+(msx$YaCqt*bR4mB@Z)1gtb@hv_rxhz74L>b>^M(5;-{w%PYzC>i9dCo zHK*qAQ;Ks+T$oZ88QPCJV*APQ@$}S#KA9tRmP6uM#HO^LxM`Biq(hbrQlGj(7@yI! zpN8>Fx%v@~W1717jLw!J>r>zTJWJwPD$v7m7|<-AIRj{qBR3tyRZHDzoTeC2oY5-2 zDdrUS3SFw0n8(1{Hh|uxIf6f$)p)TK#)Mu~-6eC&Zcqo@E4$sp_L1{+)a|Nt;-mH`aC@u| z9XXE=cQ*bb{F!y?-~8fT=+vJsEpI+}(0Eqpntc($BGb+27Xupm^w#6@gfqGoxgFyk zZ9>kSUw(9Ng^B#t*+l(adI5c}c5UV3(uB;a0m?{m8|2j%GUm+0gD0Iw58IIU#A$b$ zbz@YqOahT}()JSX&-B#|}8z!-L0%2S-kGtU)K8Vo?J(f8M|C z*5ZVWx@sRkj}lC#ID;K!VR_TpY6yk-l>qS|Pl{)^QY&+n`gRe-~S;ip|L8gG#ZWjm%*-2sG(RsQdAFV zmSL!}YV0b?u55P{d0(;jHGNMrbyd+G$nw4{-;Fo+3>)bP4deZ!*N-U7z*{hty@2_0 z>jI~(Ac+V~vz8XOdO14p>1n2TvFpi~E-|_nQLpREaGk}iPAl*A!%XwS>`S}FCeoIo z8NN+rL&Mmj*fWu3djX|hU{K#Ti5(~^)(O>pOt57s7V*6xu&|9)%kpU8!%%j0EoFX} z_ON>aYQUsW+E<#=S%M>JUkbu25fdUcB{u41(*Dn{rI0`bNpX-va52Y*=Ry)Z_fx23 zUL_FoF6?55S1doJgysLNDdl00!fX#_9)1$Dz<3ZvO{s^kV0M{P$PZ!4O|vX&n7=CM zJnV(LthDjMaK9ftpHf(<=Imuc%HedKRd-l_>EH#q^TB?Kx7G7ye<{rhr42B z#R{m+hou~W5*7BymO8^k(4s5>P17WWkb*5Jo5OOUSo^V5(=1i9Z55eAOJxFpLzs16 znMK!4S=UEd$|-yw=UrAxQJYp_DJ_*KV8q14supM%=@yj(iw4xtJ?fd3x~qUeXM-r; zW9u+hK1lJq?ijdPKb(hN}=JAVj2Wd=oX%0s;Z??f(@$4nu-;Vn7$R52J&_ZSA? z5!~9}#!2ZgvO?=C>JQkS35-o5jDppot#lhxWKZO(lU?m%tdJlGG~YHY%>);rYsjVu z7g~TzVk599!F~}7VA>2=k(2H}%acAh11hjtNxmxFq+!M%@DXln}d3Q;SA%I5T*=WKEzDzawjrokGo zpVA--&$?p;ZXR&x2fde0988_dX&p^|0IG|lg zg;w_B)_hpZ%@ji!8sX}}KmL0D!S6T!w79VF9-D}6{7Zljzc>L-fD_;ZH~~(86W|0m z0ZxDu-~>1UPT*Z9@Zlm_ow6e^T>pP7Y<&B!SCH$^32*|O04Kl+Z~~kFC%_4C0-OLR zzzO_+68PTY$?Dzr?__;CI2>tC)n*Z#ToI|$+zC%_4C0-OLR zzzJ{yoB$`l32*|OKuw^%x+0(xMROy2J7`e~YJmT#cf^5A?x3_pu;eRI4#pENkbqgwK<+K!|x%_&Vc;ef$Q zrQMrP8k|8xT6rL4N=+B%lLiM*;Izt1bz7KInr5rYZ7Ui6)ChrAXv{0klB+{8bxmt& z#rgk@e+%&87bn08Z~~kFC%_4C0-OLRzzJ{yoB$`l3B3CRzS~%Q(5Q9*%AZ19{Vx{2 B*|z`y literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/.locks/wf-5dae0f0d596348b5.lock b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/.locks/wf-5dae0f0d596348b5.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..83989dce992ff4a64a3377379afe163caddd2b5b GIT binary patch literal 24576 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/.locks/wf-a5e80d4b34c1400b.lock b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/.locks/wf-a5e80d4b34c1400b.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json new file mode 100644 index 0000000..2f5816b --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-a5e80d4b34c1400b", "node_id": "draft", "lease_id": "cd65d7ffec9c444ebad6e7dfc7917d09", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "dbf8bb72f4bd941ee3c5bebc09a7950a2718ce2f798c1dede915a1a87d0bb374"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json new file mode 100644 index 0000000..d3dbefa --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-a5e80d4b34c1400b", + "node_id": "draft", + "lease_id": "cd65d7ffec9c444ebad6e7dfc7917d09", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..c8dcfb640e8f999102705b19f4d88d641763e9f6 GIT binary patch literal 24576 zcmeI4&2Qtz6~HNZZN~v@H7F7X$iW1Av9bE#r%3t~*Jg@EWpC=$0=ozn#BgS4F{Vg` zr2Mf9qu91cPrdcp0=?$YL(e_*QlNi8b1Cu<6#WN!>>G;Ihpk=9i}YY*2nkCLXWq>G z-h1G-C)f9&ja9kILj(~cvKlf~`##W`2`-VKP@b)I#_&yOD+?j1iDf9^c5 z&n?e4QV_~Fm?SPI-$uR z38IBc4MG$}G;z@hU93PlpxFH~jiW_6&__|=(==PS0JI*3ZZeEYP2EsVT?{EoY5Be` zW)$`Fwv=z;bLh}aN{^x>^FX0RYC_L~G=MHVI_f$*XLK^rCc^)Ggd#obaGq{T3~I?l_cR?V zx-5IDthO79(va=0BJV2pu3rHxdrp)GnkyW z&QQ|w*zaK(u9K+MZDsvIkm_ELzO-9x)@~_! z8{1TF>j+sCd8TIBo=>Ugw<*RZv3*5F2B8K<1X-415$yTCg>0l+mPdUIar5Gpl;R%k zBlirN0W(l(SE)-UF$$$!$qz0>M2J+EScpxf-7j8A0f84JL4F3w`Gg*xb4l<#NaFq= zD>H~$54OyM3s#;|!pgs>OIgsTFtvnf4vr(17!AX)F7?p`Ol30)_bIlquIto8q)koYW7AelBHIlG z3_2Z#S(7dLm@trnd+s~RIuF=#vzf~nbta^+%;+IlwSxJIUr4Z4!p*^@^a83vI1A3$ zIx06ln8dQy-`-*-*c(P9gc!)WfokOxuu=YAP0{s&CpB zRtP23Rt%&d3m|x&X6jQy$Nrd`+b{(sy9xMA0>z+(Su&(o&;;fmGlo)(GECB&%w)=o z-oaGZkY?Buz0aCTVR3Vf0owXKnZkmSZZ(AOS^q`n3$wUIgFXsVdc|~jyL-yAZ5h@r z%%`>lm$9Ovq4a#&29xi zqTG~6W;)q(fz$#Nj7otI{^yp%T#-&MC?2x?5C{b#1f6n0RygOJ*v!m*Txy){qM>+- zuOS~BZKUhS(=6m`n(Ujodw}D!fG0}eGc19%V#unLU}KJ4j*{X5xE*4{(zx0Ghp``G zVV1<9%eKl~5}d$()}+1=q8^KiEHaeaKI8ByYHb;IlJWpLKnZA&LhKgxBB@Z4cbTyO zE-JEa8D^WQWROrl3{HAe6=ey+5crC?GeSXvl1UPJk2O1ULas;Jy?1Xh~a}v%NH0|9>aczPs-o1U zPJk2O1b&zVezJ79cJucFvp?-*zyDv^s0+2f)JW~4jel&sga>|c0-OLRzzJ{yoB$`l z32*|O04Kl+{GbVRR#pY=upxXf=l_v!t~1#YK}*()Ga2PUhh}k#=hanb`EJtS9G6|5 z%9@~a#v|> zQ@-A|v9$dBf9*d4eE7u)Z~~kFC%_4C0-OLRzzJ{yoB$`l32*}UpTLhROAjmMenRo5 H5Lf;OALy}<8fiZ4NivgmQ3RzhgGdzlNdEZv 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bindings.json b/tests/fixtures/persisted/workflows/v0.3.0/bindings.json new file mode 100644 index 0000000..e93efde --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bindings.json @@ -0,0 +1,242 @@ +{ + "release": "0.3.0", + "box_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpl1eck1pe", + "hostname": "frozen-host", + "tree": { + "calc.py": "def add(a, b):\n return a - b\n" + }, + "spec": { + "schema_version": 1, + "goal": "fix add", + "final_node": "fix", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix" + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ] + } + ] + }, + "scenarios": { + "planned": { + "project_id": "frozen-workflows-planned", + "workflow_ids": [ + "wf-dd2f5d4d4a694390" + ], + "states": { + "wf-dd2f5d4d4a694390": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "completed": { + "project_id": "frozen-workflows-completed", + "workflow_ids": [ + "wf-7339a776c4604b99" + ], + "states": { + "wf-7339a776c4604b99": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-7339a776c4604b99-final" + ], + "run_artifacts": [ + "wf-7339a776c4604b99-final" + ], + "wal": [] + }, + "no-changes": { + "project_id": "frozen-workflows-no-changes", + "workflow_ids": [ + "wf-e7d96a830ad54285" + ], + "states": { + "wf-e7d96a830ad54285": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-e7d96a830ad54285-final" + ], + "run_artifacts": [ + "wf-e7d96a830ad54285-final" + ], + "wal": [] + }, + "gate-failed": { + "project_id": "frozen-workflows-gate-failed", + "workflow_ids": [ + "wf-5e5335a66e3e4a5f" + ], + "states": { + "wf-5e5335a66e3e4a5f": "failed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "failed": { + "project_id": "frozen-workflows-failed", + "workflow_ids": [ + "wf-0a91dd8ccd0a4561" + ], + "states": { + "wf-0a91dd8ccd0a4561": "failed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "cancelled": { + "project_id": "frozen-workflows-cancelled", + "workflow_ids": [ + "wf-8182839e1fba4058" + ], + "states": { + "wf-8182839e1fba4058": "cancelled" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "running": { + "project_id": "frozen-workflows-running", + "workflow_ids": [ + "wf-f212e2feb55a4cb8" + ], + "states": { + "wf-f212e2feb55a4cb8": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "47731@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "recovery-required": { + "project_id": "frozen-workflows-running", + "workflow_ids": [ + "wf-f212e2feb55a4cb8" + ], + "states": { + "wf-f212e2feb55a4cb8": "recovery_required" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "unapproved-completed": { + "project_id": "frozen-workflows-unapproved-completed", + "workflow_ids": [ + "wf-17969765182e4464" + ], + "states": { + "wf-17969765182e4464": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-17969765182e4464-final" + ], + "run_artifacts": [ + "wf-17969765182e4464-final" + ], + "wal": [] + }, + "unapproved-running": { + "project_id": "frozen-workflows-unapproved-running", + "workflow_ids": [ + "wf-2d9b5846bc1b4adf" + ], + "states": { + "wf-2d9b5846bc1b4adf": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "48048@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [ + "wf-2d9b5846bc1b4adf-final" + ], + "wal": [] + }, + "created": { + "project_id": "frozen-workflows-created", + "workflow_ids": [ + "wf-715ce7479b854ad2" + ], + "states": { + "wf-715ce7479b854ad2": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "revised": { + "project_id": "frozen-workflows-created", + "workflow_ids": [ + "wf-715ce7479b854ad2", + "wf-22e223bfd8e74689" + ], + "states": { + "wf-715ce7479b854ad2": "planned", + "wf-22e223bfd8e74689": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + } + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/.locks/wf-8182839e1fba4058.lock b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/.locks/wf-8182839e1fba4058.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..eacce043bc5e90602a9df24d002758696d8ea894 GIT binary patch literal 36864 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 MYq#Y3wBP@K2?L{{9smFU literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/.locks/wf-715ce7479b854ad2.lock b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/.locks/wf-715ce7479b854ad2.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/wf-715ce7479b854ad2/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/wf-715ce7479b854ad2/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/wf-715ce7479b854ad2/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..a42ec91bfb03b463eeafe666ee8c034e74e2e064 GIT binary patch literal 36864 zcmeI4Ur*as6u=#z5RibqteLh4uDpj55gI$r|CcQTcR~xHr9o;nP1cR?HF0t5)OLWb zLKEAxP1-}h!9GC0$G$~B!1fLHxMRo8KSD_#riu1k1!3QN?mg%J&hc@40(tzXZu*=U zj@zL=QG}0#xjErWLIgpWgP&#iiQ8Q`Scp&HcVguDy2Cl){x9j()z?BYnGlw@*8f~p z*IutZN&d063LV&x01`j~NB{{Sf&YiV<)_J|)E66xOWm{?KR#Z8tO3~MG! zXGqtgcC0gfxw5#F%4QRnJ3eh$Jk}B`@vd|%MaDI2U)9y&%8@#cj3_2e^+}UFJlxx_ z9X=)Bs82J*qMje?hK)7Vuhm1+IB1f_QN5o2EtObGZEPf7U5ptsFv6RgsvMNAkST3o zyUDJaGIfUTa_V!Y!5g*E_Smw_ROw!qy@RypgYYcNy1eUXuH(#PgXi>Iooj|^A9J_s zn)Xbyf)KE|t3mY5Ho@z1U3=y^_ADFdP}?*(IC|CzU_G@ow`W6uh6=9WhREU6bSw3wNo1lkhXAHMEFv z%7UvP2p+h8>lva`%hGhG2Z$#_eN?4scYD6(J15-s-YV$y{23*E#|NOg13u_^EHr0u zm5r(nQ-R}}$L54%2SWFvP`L+aqI4DcXgrZzZ@3JC)P*?HCa=f77d4mbj>|Z^ehDe9 z&qJ1i6nbYOg+>=_yt20tC5XwK8Oe?3%8{#m7jkBL?V+Ft>F3uU{j&J#OoK;&gE= zZB7hs+O)$nFf;w*Ckso#l0O}f?O)@k;JrZ-j!y54tvN8WFH1IIy@5YR?f|5A)I8X0 zz}$Vc(Hy%og;(h?N)Ocs>Y>`$Q6CR(GT!Du4cy2AU+$`P6>gL}wZ}WPT{Sb}(DC#g zTBZ3fx|0dmcGxhfbmzEo+I z3wg?NIVo4%mgQ}!(v+oYPO9c}_lqStmn&>ZQdN>}8t*^3u`E;tPNDRWJ*95ea4ZJZ zrflBMc6{ZeQ*b@GWos?zOk>6Nsnu!g60~DG+syi%t|fE*MDFrzP?IRj`J!IoQZY|y znbTHDDOXws=Pjeib-lzYhMc1X#tS-Sv|N_UOm7)RnO117TyAk=`=#jVZQh}A0Z6RM z8S&VmmRJ=H^PEt|#EclcCHKVYw=YGL!2olq;X`*=s(^DHhR@-=>pGpT9}5t_4IibJ##TTzlbA_TORWDbzzZ7^Kmter2_OL^fCP{L5Yk literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/.locks/wf-0a91dd8ccd0a4561.lock b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/.locks/wf-0a91dd8ccd0a4561.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/commit.json new file mode 100644 index 0000000..f90d636 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-0a91dd8ccd0a4561", "node_id": "draft", "lease_id": "06221684c085442e8d9a18c7b78dabe1", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "579e7429d976e8c201a9fdbd6c5041f7bf2d2b69b67c441822bd40c7f344c816"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/result.json new file mode 100644 index 0000000..d9ccfdd --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-0a91dd8ccd0a4561", + "node_id": "draft", + "lease_id": "06221684c085442e8d9a18c7b78dabe1", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/commit.json new file mode 100644 index 0000000..2e441f4 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-0a91dd8ccd0a4561", "node_id": "fix", "lease_id": "06221684c085442e8d9a18c7b78dabe1", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "8b5adc2a3a23b87acc6b723e25ccee1331ca6b576e3c8887869ad03d7d8cafa4"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/result.json new file mode 100644 index 0000000..92ad19a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/result.json @@ -0,0 +1,19 @@ +{ + "status": "failed", + "agent_id": "kimi-code", + "output": "", + "error": "kimi (fake): refused", + "exit_code": 1, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-0a91dd8ccd0a4561", + "node_id": "fix", + "lease_id": "06221684c085442e8d9a18c7b78dabe1", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/stream.log new file mode 100644 index 0000000..899289d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/stream.log @@ -0,0 +1 @@ +kimi (fake): refused diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..717702af78d27c972bb700cb2e0fc71bc459e59f GIT binary patch literal 36864 zcmeI4&2QvL6~NueOp+dy1cWr!yOyT8)ebYKvgoo98F?)rH3>Q}Gc ztE#s9*@t^!#>5~_S|k%S;p@W8jPOHI6a--g{;Kd-UJv2Tz48P6uMEAv>FtcL`OCG% z#ZQHWg^EyJU;gW2W9ieS=L^4IT7(3=kN^@u0!RP}Ac6l6fvfK<%&+|5QRT`H+mv0L z$H{3B#pkITg>6QTnR~{PG>qH*jOxx|W4qZ9j}9Jxc+?OdAM7@si~Zd3YT}cFLFVet zlkL66vz^9jDLkaLezqIZwVK$8NV{xv?drkY{7SuEx!TEy7qPOHvXS&!!hE4zbNfeo zje+E%xl|l#SZp?)H^rxikN39^Ux*(xUev^hq*>WEbgbFt~r>ezGpv@8&tZ%vsX@~jl$7$T2rUNa~4g&@Q zJ#7Who^d)9Zz3(Nl! z7I+|gdVg+ZeZBG`*R>FpvQN6K?X$6ub0cnZEOWI@TC4^&*M9b`d-Gi6&yPp?@3J!( zy}lDJKHeXZ*;lhyR$K*n{qY=%0OIyh^U31_sJq`dXpTgte3lNZ^sw=M#w(qLAs_c{NX4E zQK<^^l}csdyINJKo(CH;aTH2z-={Ls4O3BN)!a~&4cTcb@|FrOZPPFu#gx}&c}teB z=Np@jqg%Gt%MVBxFt`gX2XRDU(^c2o^;V{xwu~fIyteDf=Por*&Z5?d zFT-`*t~cvhs}m{AKUF%c&X?x4qMN?MWK$=^X2i2J+wlU%yuf6>Z&4>uRANwO_=FPM zR&45fL0}VysJ88~z=x1?@mfm#6V@W-UP#(fYSM8`B56ws!V8g5D%B(&ol%Xi?S=eH*esohHQs;^WFrP!LSAwJN`ysiISjlIj3_U_k|a)`TFw@f z&0qtP?QOR3AIsw$?f z`?785y2@lw~B7d{qxhSN5+BFJ%_hE5>(WEyrjN**_jP2P%P z4_YZb7WfSj&R^R63{cE{2pD6Fz_9>CtT^ETg&)!I9C5@a@-vPU4jJ+p-k&o0+!#LB zfCLpZ967-#(nilJAW`0wdsA$1CEO(?=bnebihkR;ql2(Ls>$6h}=cs?D3#s%8iLi z)0w|vtBQWdNa#RN8#4d@&XzA3l3olqCDray8ZV*pMLN7kAIn+nJJFR z&Z7;g$$?=plK~T*I{E`x9HE-a*tK2_OL^fCP{L5&j#10Jt0_vY>>&6M@>uK5Yl?%z?GrfWv=M9~Ck_wFVQo|-W&>&BkW-c6dR z%Cc5Gx->zznY&3dY|YS%M+f@SdbjXX7=_ARrKy@zJi2pTTK@e1@)rWU@InGe00|%g zB!C2v01`j~NB{{S0VIF~-hKk#tjw-g$`gR%cM_|;{r~5}^5<`V2eAi800|%gB!C2v z01`j~NB{{S0VIF~-gN>GEByI?{QbXo-43w-NB{{S0VIF~kN^@u0!RP}AOR%s6(^AY v{@+defBgRcD?a+zJtTkxkN^@u0!RP}AOR$R1dsp{ct;4V+|#O)zW@I>W^%W9 literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/.locks/wf-5e5335a66e3e4a5f.lock b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/.locks/wf-5e5335a66e3e4a5f.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/commit.json new file mode 100644 index 0000000..34aa8ab --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-5e5335a66e3e4a5f", "node_id": "draft", "lease_id": "40c036e29e344451a6dbb8fccc30d95f", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "e41c5eb0d5c1a9788c0dd8ce70052f6c66d274027911a8926d2801b5a89da3ce"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/result.json new file mode 100644 index 0000000..f2f806a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-5e5335a66e3e4a5f", + "node_id": "draft", + "lease_id": "40c036e29e344451a6dbb8fccc30d95f", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/commit.json new file mode 100644 index 0000000..6c0c280 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-5e5335a66e3e4a5f", "node_id": "fix", "lease_id": "40c036e29e344451a6dbb8fccc30d95f", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "968d3a8caf7ad122f3df80f923d0a8573b4d7515b279aa067f405bc2b415e9c8"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/result.json new file mode 100644 index 0000000..f60576f --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/result.json @@ -0,0 +1,33 @@ +{ + "status": "failed", + "agent_id": "kimi-code", + "output": "kimi (fake) wrong.", + "error": "quality gate failed: 0/1 checks passed; failed: python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": { + "passed": false, + "score": 0.0, + "checks": [ + { + "command": "python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "passed": false, + "exit_code": 1, + "output": "Traceback (most recent call last):\n File \"\", line 1, in \n from calc import add; assert add(2, 3) == 5\n ^^^^^^^^^^^^^^\nAssertionError", + "duration_seconds": 0.03 + } + ] + }, + "patch_sha256": "93a267aca73ce9b58f03c7393a75925def1745c1921d6862043aecb70be4196d", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-5e5335a66e3e4a5f", + "node_id": "fix", + "lease_id": "40c036e29e344451a6dbb8fccc30d95f", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch new file mode 100644 index 0000000..b78cd53 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..7f32081 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a * b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/stream.log new file mode 100644 index 0000000..f930192 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) wrong."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..4538deee877dfdb17e8f5021e68ef13307615edf GIT binary patch literal 36864 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/.locks/wf-f212e2feb55a4cb8.lock b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/.locks/wf-f212e2feb55a4cb8.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json new file mode 100644 index 0000000..dfea5ae --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-f212e2feb55a4cb8", "node_id": "draft", "lease_id": "89f3905b7269463784e77b1bdf6d8684", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "e7af9f29c5e2970e638609f7b559f96148b877510ca9e342bdfc73797d3e6e86"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json new file mode 100644 index 0000000..38a5c9b --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-f212e2feb55a4cb8", + "node_id": "draft", + "lease_id": "89f3905b7269463784e77b1bdf6d8684", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..f06d9231e95a6c78b18670ed1a92eb1b2595cc99 GIT binary patch literal 36864 zcmeI4&2QVt6~HMwvFrf0TNE1?-9rfVurVS_r1){o_?_7r%d#sxI3UV{1|%%{T)()pP+qIoOBdTIaROHKQgo7BDk$g5wYGk` z(;Q09yGz88j>T5|GD^!=JTo;kR&bohJm%3KWpxbdyiY<-oeh!`XARy z<+XeFN`E>ZQ)Zwqz==6>_S2dqn7DjXjYwCyVs>RsGX!x zc^0uT>qd4QMKkG;M7`K$w&#b3EbhjBI8!U`0wIfS@V?m^Bwgm(A0<&Z%K$ng^gRZK zp0xpJPXasch2WrB2bXYz=P;76;(Ap~8R=wIi!0Fy3}~#R%}AU&V9>lYX2(9CXP5j= zeAs+{>tLrPN`)}M!eWwMSNWsJ8EQlsp?nr_=YjdVQx#nj1hyOX0P&=&k9O&Y-Ck;^ z(Gd%i*H(;r>5MhgCp5AMHWg-R53vY-P%3>Cj8}o9{RGn|s^Mr-MZ% zxj*Oz7BX(jhs~WPER@?@Pq()oHmf5B9S`4`R(5*UoeaQXM2A78OU{Cb&?#YSU7lN5 zYu>xgZpqMI$vl`Sv(qxXDc$~;053d{01`j~NZ|jSz{fu?iOTNoyBGH^me+pw_|tcU zU7@~PS~@6~N~Ohjl_g>6#M|&1a)UKI=4cxE_-@vsp11gOIU8bWNw~6w>N;jaYUmrX zydhaFS=ww!n~Gf18oH&a4XdQBZV@NdW z$0V+MQ9vQLtgB(YlPX6YEly-7v>oZhrh5B0=(Jr4j>E9ts;8Z9AT#$!?y@@1KulTH zU5iP&N{GpbV<@KOc#Juo&Ro}^mM1reMw#XkN=#EWsq1*2Ni5PZO^10dtgNT6Dv8@> z9a5xxl}))?IgCh9*{pc}sYocTR4aTDPAZ!pzN+{XF3^~GDHLbP37lt=;Jh10oo-rG z5Yskns{5zBJ!6!&e^ITZeuu$rvYh~zkQau%AgES4

hpQwHs!Og^vXUG>`+9Ub`{ ze}nfn+8OC~gR?1}l$5c&4(W$3dvgOK(+Ax(0h|D32uX4pV;a!%J z@(x@e8!*UxdCegx5$@d!Gw@1aQC@+?aTG(dj4ddeLY|c+&0N%Fk5y`hZm7D__gF3f zIQU8XwM|Tz1g17@lIK2MlD2s-*=XvSr_@c$Q!Ghy3}^z~7^cb$!;u~8>D1IsbwdV+ zPI^IFAEdZ#`%B6?@pdV#jp&jFrAtDh|qk?36$aP)=T&{RFQ{4VH)??Ib7iLW6V)te*vuD>t5q3BQxns zxQnUDswEF?KkhEM*G+W@(Dy5HhZ!YZ>kMD959EW-UE>-ndL&5LIk)5WQ&dwWRT-IY zQ<;x!ad0;to$7M7cquh5M;)z8mG^Y#U z%=cj8+C_ZK%h@S&dwf#})({a^tC4IJ3Mvyf1blht+7DXglmEm!-H@3@CD$RIDrslq89NB{{S0VIF~kN^@u z0!RP}AOR%s1`zmVxhyEVTf%pzeCPjiKA1hS2M^}-zkXgkHBh_s?B1*Modqs;V@A13 z4W5+I%>2Qsq1;{=!q0(#wA(k7)_`Y)q)9CY(r(>M8a(u+>DHCf=5Hnqo-5H5{Yq(b zH6Yuy#vXn)dns{{WzmY-|7k literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-22e223bfd8e74689.lock b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-22e223bfd8e74689.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-715ce7479b854ad2.lock b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-715ce7479b854ad2.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-22e223bfd8e74689/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-22e223bfd8e74689/spec.json new file mode 100644 index 0000000..fdc8257 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-22e223bfd8e74689/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add, revised", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-715ce7479b854ad2/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-715ce7479b854ad2/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-715ce7479b854ad2/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..ac340c4f7929ea9d4921223a9cfa5c22cecc16d0 GIT binary patch literal 36864 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 literal 0 HcmV?d00001 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/manifest.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/manifest.json new file mode 100644 index 0000000..fdab079 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/manifest.json @@ -0,0 +1,36 @@ +{ + "schema_version": 1, + "run_id": "wf-2d9b5846bc1b4adf-final", + "project_id": "frozen-workflows-unapproved-running", + "parent_run_id": null, + "created_at": "2026-11-09T10:20:41.704064+00:00", + "updated_at": "2026-11-09T10:20:41.705003+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpl1eck1pe/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "e6e016c0b190badd6a5006c39df2a7ab1284fcaf", + "task_id": "9cb2b623-8249-4410-8ed2-b0688f868b61", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-2d9b5846bc1b4adf", + "node_id": "fix", + "final_agent": "kimi-code", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "97928fb02fee6bbc1961826ff73df1c85ced33f7e8cc227186d365c9032e4dcb", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba" + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/result.json @@ -0,0 +1,7 @@ +{ + "status": "completed", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02 +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/.locks/wf-2d9b5846bc1b4adf.lock b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/.locks/wf-2d9b5846bc1b4adf.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/commit.json new file mode 100644 index 0000000..10e1fe3 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-2d9b5846bc1b4adf", "node_id": "draft", "lease_id": "c500c9753a704e038f3b6be8f94f32f3", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "50088a734f61751b71487bed3dcec1ace63b48f39fc37f4146c44656f2471938"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/result.json new file mode 100644 index 0000000..5a84d9a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-2d9b5846bc1b4adf", + "node_id": "draft", + "lease_id": "c500c9753a704e038f3b6be8f94f32f3", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/commit.json new file mode 100644 index 0000000..4ae4eda --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-2d9b5846bc1b4adf", "node_id": "fix", "lease_id": "c500c9753a704e038f3b6be8f94f32f3", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "97928fb02fee6bbc1961826ff73df1c85ced33f7e8cc227186d365c9032e4dcb"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/result.json new file mode 100644 index 0000000..b9cfe8a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/result.json @@ -0,0 +1,33 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": { + "passed": true, + "score": 1.0, + "checks": [ + { + "command": "python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "passed": true, + "exit_code": 0, + "output": "", + "duration_seconds": 0.02 + } + ] + }, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-2d9b5846bc1b4adf", + "node_id": "fix", + "lease_id": "c500c9753a704e038f3b6be8f94f32f3", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) fix."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/workflows.db new file mode 100644 index 0000000000000000000000000000000000000000..5baaa2f4ca432350ff2b40777b1c69eeb71c423e GIT binary patch literal 36864 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 h2_OL^fCP{L5//`` were +written by that release's own ``syncmill workflow`` commands (see ``freeze.py`` +there) and are never regenerated by the current writer. Each scenario is one +project: its workflow database, the workflow directories (spec, planner +output, node bundles, stream logs) and any workflow-origin run artifact. +Workspaces and job records were not frozen. + +Only machine-bound values are rebound, each in a named place: the repository +root and source fingerprint (recomputed in the stored value's own version) of +every workflow row, a lease owner's ``pid@host`` (set to a pid that cannot be +alive on this host), and the root and fingerprint of a final run artifact. +``spec_json``, node bundles and ``commit.json`` are never touched: digests +cover their bytes. ``_install`` asserts that every other cell is unchanged. +""" + +from __future__ import annotations + +import json +import re +import shutil +import socket +import sqlite3 +import subprocess +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from syncmill.cli import cli +from syncmill.worktree import fingerprint_version, repository_fingerprint + +FROZEN = Path(__file__).parent / "fixtures" / "persisted" / "workflows" +RELEASES = sorted(p.name for p in FROZEN.iterdir() if p.is_dir()) + +#: scenario -> the workflow state each row was left in, oldest first +SCENARIOS = { + "planned": ["planned"], + "completed": ["completed"], + "no-changes": ["completed"], + "gate-failed": ["failed"], + "failed": ["failed"], + "cancelled": ["cancelled"], + "running": ["running"], + "recovery-required": ["recovery_required"], + "unapproved-completed": ["completed"], + "unapproved-running": ["running"], +} +#: v0.3.0 added plans from a document and their revisions. +SCENARIOS_V030 = {"created": ["planned"], "revised": ["planned", "planned"]} + +#: Within C ``int`` (``os.kill`` accepts it) and above any real pid_max, so +#: the liveness probe answers "no such process". +DEAD_PID = str(2**31 - 1) +GATE_COMMAND = 'python3 -c "from calc import add; assert add(2, 3) == 5"' + + +def scenarios(release: str) -> dict[str, list[str]]: + return {**SCENARIOS, **(SCENARIOS_V030 if release != "v0.2.0" else {})} + + +def _bindings(release: str) -> dict: + return json.loads((FROZEN / release / "bindings.json").read_text()) + + +def _cells(conn: sqlite3.Connection) -> dict[tuple[str, str], list]: + tables = [r[0] for r in conn.execute( + "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'" + )] + cells: dict[tuple[str, str], list] = {} + for table in tables: + columns = [r[1] for r in conn.execute(f"PRAGMA table_info({table})")] + rows = conn.execute(f"SELECT * FROM {table} ORDER BY rowid").fetchall() + for index, column in enumerate(columns): + cells[(table, column)] = [row[index] for row in rows] + return cells + + +#: The machine-bound columns of the workflow database. +BOUND = { + ("workflows", "repo_root"): "path", + ("workflows", "source_fingerprint"): "fingerprint", + ("workflows", "lease_owner"): "owner", +} + + +class Frozen: + """One release scenario installed as a project bound to ``repo``.""" + + def __init__(self, release: str, scenario: str, tmp_path: Path, monkeypatch): + self.release = release + self.scenario = scenario + self.bindings = _bindings(release) + self.facts = self.bindings["scenarios"][scenario] + self.dir = FROZEN / release / scenario + self.root = tmp_path.resolve() + self.repo = self.root / "repo" + self.repo.mkdir() + for name, text in self.bindings["tree"].items(): + (self.repo / name).write_text(text, encoding="utf-8") + for argv in ( + ["init", "-q"], + ["add", "-A"], + ["-c", "user.name=t", "-c", "user.email=t@t.test", "-c", + "commit.gpgsign=false", "commit", "-q", "-m", "frozen tree"], + ): + subprocess.run(["git", *argv], cwd=self.repo, check=True, capture_output=True) + self.runs_root = self.root / "state" / "runs" + self.project_dir = self.runs_root / self.facts["project_id"] + self.workflow_ids = self.facts["workflow_ids"] + self.workflow_id = self.workflow_ids[-1] + monkeypatch.setenv("SYNCMILL_RUNS__ROOT", str(self.runs_root)) + monkeypatch.setenv("SYNCMILL_WORKSPACE__ROOT", str(self.root / "state" / "workspaces")) + monkeypatch.setenv("SYNCMILL_PROJECT__ID", self.facts["project_id"]) + self.fingerprints = { + stored: repository_fingerprint(self.repo, like=stored) + for stored in self.facts["source_fingerprints"] + } + self._install() + + # ── installation and rebinding ────────────────────────────────────── + + def _rebind(self, kind: str, value): + if value is None: + return None + if kind == "path": + old = self.bindings["box_root"] + assert value == old or value.startswith(old + "/"), value + return str(self.root) + value[len(old):] + if kind == "fingerprint": + return self.fingerprints[value] + pid, _, host = value.partition("@") + assert pid.isdigit() and host == self.bindings["hostname"], value + return f"{DEAD_PID}@{socket.gethostname()}" + + def _install(self) -> None: + shutil.copytree(self.dir, self.project_dir) + db = self.project_dir / "workflows" / "workflows.db" + conn = sqlite3.connect(db) # replays a crash's WAL, as any open would + try: + before = _cells(conn) + for (table, column), kind in BOUND.items(): + rows = conn.execute(f"SELECT rowid, {column} FROM {table}").fetchall() + for rowid, value in rows: + new = self._rebind(kind, value) + if new != value: + conn.execute( + f"UPDATE {table} SET {column} = ? WHERE rowid = ?", (new, rowid) + ) + conn.commit() + after = _cells(conn) + finally: + conn.close() + assert after.keys() == before.keys() + for key in before: + if key not in BOUND: + assert after[key] == before[key], key + # A final artifact carries the fingerprint at the top level and in + # its plan, and the repository root once. Exact counts. + for manifest in self.project_dir.glob("runs/*/manifest.json"): + self._replace_bytes(manifest, fingerprints=2, paths=1) + + def _replace_bytes(self, path: Path, *, fingerprints: int, paths: int) -> None: + data = path.read_bytes() + found = 0 + for stored, live in self.fingerprints.items(): + found += data.count(stored.encode()) + data = data.replace(stored.encode(), live.encode()) + assert found == fingerprints, path + old = self.bindings["box_root"].encode() + assert data.count(old) == paths, path + data = data.replace(old, str(self.root).encode()) + path.write_bytes(data) + + # ── reading ───────────────────────────────────────────────────────── + + def rows(self, sql: str, *params) -> list[dict]: + conn = sqlite3.connect(self.project_dir / "workflows" / "workflows.db") + conn.row_factory = sqlite3.Row + try: + return [dict(row) for row in conn.execute(sql, params)] + finally: + conn.close() + + def row(self, workflow_id: str | None = None) -> dict: + [row] = self.rows( + "SELECT * FROM workflows WHERE workflow_id = ?", workflow_id or self.workflow_id + ) + return row + + def frozen_bytes(self, relative: str) -> bytes: + return (self.dir / relative).read_bytes() + + def cli(self, *argv: str): + return CliRunner().invoke(cli, [*argv, "--repo", str(self.repo)]) + + +# ── inventory ───────────────────────────────────────────────────────────── + + +def test_every_release_freezes_every_scenario(): + # A release directory that lost a scenario would silently shrink coverage. + assert RELEASES == ["v0.2.0", "v0.3.0"] + for release in RELEASES: + bindings = _bindings(release) + assert bindings["release"] == release.removeprefix("v") + assert sorted(bindings["scenarios"]) == sorted(scenarios(release)) + for name, facts in bindings["scenarios"].items(): + assert list(facts["states"].values()) == scenarios(release)[name], name + for stored in facts["source_fingerprints"]: + assert fingerprint_version(stored) == 1 + # No release wrote a v1 or v2 manifest: both normalize to v3 + # before the insert. + db = FROZEN / release / name / "workflows" / "workflows.db" + conn = sqlite3.connect(f"file:{db}?mode=ro&immutable=1", uri=True) + try: + versions = {json.loads(m)["schema_version"] + for (m,) in conn.execute("SELECT manifest_json FROM workflows")} + finally: + conn.close() + assert versions == {3}, (release, name) + + +@pytest.fixture(params=RELEASES) +def release(request) -> str: + return request.param + + +def _each(release: str) -> list[str]: + return list(scenarios(release)) + + +@pytest.mark.parametrize("scenario", list(SCENARIOS) + list(SCENARIOS_V030)) +def test_install_rebinds_only_machine_bound_values(release, scenario, tmp_path, monkeypatch): + if scenario not in scenarios(release): + pytest.skip(f"{release} has no {scenario}") + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + for row in frozen.rows("SELECT * FROM workflows"): + assert row["repo_root"] == str(frozen.repo) + assert fingerprint_version(row["source_fingerprint"]) == 1 + assert row["source_fingerprint"] == repository_fingerprint( + frozen.repo, like=row["source_fingerprint"] + ) + if row["lease_owner"] is not None: + assert row["lease_owner"] == f"{DEAD_PID}@{socket.gethostname()}" + assert re.fullmatch(r"wf-[0-9a-f]{16}", frozen.workflow_id) + + +def _immutable_rows(release: str, scenario: str) -> list[dict]: + """The rows exactly as the release left them (no open, no WAL replay).""" + db = FROZEN / release / scenario / "workflows" / "workflows.db" + conn = sqlite3.connect(f"file:{db}?mode=ro&immutable=1", uri=True) + conn.row_factory = sqlite3.Row + try: + return [dict(row) for row in conn.execute( + "SELECT * FROM workflows ORDER BY created_at, workflow_id" + )] + finally: + conn.close() + + +def _lines(text: str, prefix: str) -> list[str]: + return [line for line in text.splitlines() if line.startswith(prefix)] + + +def _clock(monkeypatch, at) -> None: + """Fix the workflow module's clock, so a lease decision cannot depend on + how old the fixture is: the released heartbeat ages in real time.""" + from datetime import datetime + + class Fixed(datetime): + @classmethod + def now(cls, tz=None): + return at if tz is None else at.astimezone(tz) + + monkeypatch.setattr("syncmill.workflow.datetime", Fixed) + + +def _fresh_heartbeat(frozen: "Frozen"): + """One second after the released heartbeat: only the owner's pid can + make the lease stale.""" + from datetime import datetime, timedelta + + return datetime.fromisoformat(frozen.row()["lease_heartbeat"]) + timedelta(seconds=1) + + +#: Scenarios whose released lease owner is dead; HEAD's show/status recover it. +DEAD_OWNER = {"running", "unapproved-running"} +#: The final state each node of the frozen spec was left in, per scenario. +NODES = { + "planned": ("pending", "pending"), + "created": ("pending", "pending"), + "revised": ("pending", "pending"), + "cancelled": ("pending", "pending"), + "completed": ("completed", "completed"), + "no-changes": ("completed", "completed"), + "unapproved-completed": ("completed", "completed"), + "gate-failed": ("completed", "failed"), + "failed": ("completed", "failed"), + "running": ("completed", "running"), + "unapproved-running": ("completed", "completed"), + "recovery-required": ("completed", "interrupted"), +} + + +# ── reading every released workflow ─────────────────────────────────────── + + +@pytest.mark.parametrize("scenario", list(SCENARIOS) + list(SCENARIOS_V030)) +def test_list_and_show_read_a_released_workflow(release, scenario, tmp_path, monkeypatch): + if scenario not in scenarios(release): + pytest.skip(f"{release} has no {scenario}") + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + released = _immutable_rows(release, scenario) + manifests = {r["workflow_id"]: json.loads(r["manifest_json"]) for r in released} + + # The release's own state, before any HEAD reader can recover it. + listed = frozen.cli("workflow", "list") + assert listed.exit_code == 0, listed.output + shown_rows = [line.split()[:2] for line in listed.output.splitlines() + if line.startswith("wf-")] + assert sorted(shown_rows) == sorted( + [r["workflow_id"], r["state"]] for r in released + ) + for row in released: + manifest = manifests[row["workflow_id"]] + assert [manifest["nodes"][n]["state"] for n in ("draft", "fix")] == list(NODES[scenario]) + + shown = frozen.cli("workflow", "show", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + text = shown.output + row = released[-1] + manifest = manifests[row["workflow_id"]] + expected_state = "recovery_required" if scenario in DEAD_OWNER else row["state"] + origin = {"created": "file", "revised": "revision"}.get(scenario, "planner") + assert _lines(text, "workflow: ") == [f"workflow: {frozen.workflow_id}"] + assert _lines(text, "state: ") == [f"state: {expected_state}"] + assert _lines(text, "calls_used: ") == [f"calls_used: {manifest['calls_used']}"] + assert _lines(text, "plan_origin: ") == [f"plan_origin: {origin}"] + assert _lines(text, "goal: ") == [f"goal: {json.loads(row['spec_json'])['goal']}"] + final = row["final_run_id"] + assert _lines(text, "final_run: ") == ([f"final_run: {final}"] if final else []) + states = dict(zip(("draft", "fix"), NODES[scenario])) + if scenario in DEAD_OWNER: + # The owner is gone: the node it was running is interrupted. + states = {n: ("interrupted" if s == "running" else s) for n, s in states.items()} + assert _lines(text, " draft: ") == [ + f" draft: {states['draft']} agent=codex " + f"attempts={manifest['nodes']['draft']['attempts']}/1 needs=-" + ] + assert _lines(text, " fix: ") == [ + f" fix: {states['fix']} agent=kimi-code " + f"attempts={manifest['nodes']['fix']['attempts']}/1 needs=draft (final)" + ] + + +@pytest.mark.parametrize("scenario", sorted(DEAD_OWNER)) +@pytest.mark.parametrize("command", ["show", "status"]) +def test_show_and_status_recover_a_released_dead_owner( + release, scenario, command, tmp_path, monkeypatch +): + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + _clock(monkeypatch, _fresh_heartbeat(frozen)) + before = frozen.row() + assert before["state"] == "running" + assert before["lease_owner"] == f"{DEAD_PID}@{socket.gethostname()}" + result = frozen.cli("workflow", command, frozen.workflow_id) + assert result.exit_code == 0, result.output + after = frozen.row() + assert (after["state"], after["lease_owner"], after["lease_id"]) == ( + "recovery_required", None, None + ) + manifest = json.loads(after["manifest_json"]) + assert manifest["error"] == "execution owner lost; resume explicitly" + # The node the dead owner was running is interrupted; its finished + # dependency keeps its bundle. + released = json.loads(before["manifest_json"])["nodes"] + assert manifest["nodes"]["draft"] == released["draft"] + expected_fix = "interrupted" if released["fix"]["state"] == "running" else released["fix"]["state"] + assert manifest["nodes"]["fix"]["state"] == expected_fix + + +@pytest.mark.parametrize("command", ["show", "status"]) +def test_a_released_lease_whose_owner_lives_is_never_taken(release, command, tmp_path, monkeypatch): + # The released owner is read as pid@host: on this host the pid's answer + # is final, so a live owner keeps the lease although the released + # heartbeat is long past the lease timeout. + import os + + frozen = Frozen(release, "running", tmp_path, monkeypatch) + conn = sqlite3.connect(frozen.project_dir / "workflows" / "workflows.db") + try: + conn.execute("UPDATE workflows SET lease_owner = ?", + (f"{os.getpid()}@{socket.gethostname()}",)) + conn.commit() + finally: + conn.close() + # Long past any lease timeout: only the pid's answer keeps the lease. + from datetime import UTC, datetime + + _clock(monkeypatch, datetime(2100, 1, 1, tzinfo=UTC)) + lease = ("state", "lease_owner", "lease_id", "lease_heartbeat", "manifest_json", "revision") + before = {key: frozen.row()[key] for key in lease} + result = frozen.cli("workflow", command, frozen.workflow_id) + assert result.exit_code == 0, result.output + # (Opening a v0.2.0 store adds v0.3.0's columns; the lease is untouched.) + assert {key: frozen.row()[key] for key in lease} == before + assert before["state"] == "running" + + +def test_a_release_recovered_workflow_reads_as_written(release, tmp_path, monkeypatch): + frozen = Frozen(release, "recovery-required", tmp_path, monkeypatch) + [released] = _immutable_rows(release, "recovery-required") + shown = frozen.cli("workflow", "status", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + # Nothing to recover: the row is exactly as the release recovered it. + after = frozen.row() + assert {k: after[k] for k in ("state", "manifest_json", "revision", "lease_owner")} == { + k: released[k] for k in ("state", "manifest_json", "revision", "lease_owner") + } + # The release recovered it from the job record the killed run left (no + # job records are frozen, so HEAD's own recovery above words it as a lost + # owner instead). + assert json.loads(after["manifest_json"])["error"] == ( + "durable workflow job ended in failed; resume explicitly" + ) + + +@pytest.mark.parametrize("scenario", ["planned", "completed", "created", "revised"]) +def test_the_stored_spec_reads_back_with_its_digest(release, scenario, tmp_path, monkeypatch): + if scenario not in scenarios(release): + pytest.skip(f"{release} has no {scenario}") + from syncmill.workflow import WorkflowSpec + + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + for released in _immutable_rows(release, scenario): + workflow_id = released["workflow_id"] + manifest = json.loads(released["manifest_json"]) + spec = WorkflowSpec.model_validate_json(released["spec_json"]) + assert spec.digest() == manifest["spec_sha256"] + # The spec file beside the row holds the same plan. + on_disk = json.loads(frozen.frozen_bytes(f"workflows/{workflow_id}/spec.json")) + assert WorkflowSpec.model_validate(on_disk).digest() == manifest["spec_sha256"] + + prompts = frozen.cli("workflow", "show", workflow_id, "--prompts") + assert prompts.exit_code == 0, prompts.output + lines = prompts.output.splitlines() + for node in spec.nodes: + at = lines.index(f" prompt:", lines.index(next( + line for line in lines if line.startswith(f" {node.id}: ") + ))) + assert lines[at + 1] == f" {node.prompt}" + + exported = frozen.cli("workflow", "export", workflow_id) + assert exported.exit_code == 0, exported.output + document = json.loads(exported.stdout) + assert WorkflowSpec.model_validate(document).digest() == manifest["spec_sha256"] + assert document["goal"] == spec.goal + assert [(n["id"], n["agent"], n["prompt"], n["needs"]) for n in document["nodes"]] == [ + (n.id, n.agent, n.prompt, n.needs) for n in spec.nodes + ] + # spec_json itself is never rebound or rewritten. + assert [r["spec_json"] for r in frozen.rows("SELECT spec_json FROM workflows " + "ORDER BY created_at, workflow_id")] == [ + r["spec_json"] for r in _immutable_rows(release, scenario) + ] + + +@pytest.mark.parametrize("scenario", ["completed", "revised"]) +def test_lineage_reads_a_released_workflow(release, scenario, tmp_path, monkeypatch): + if scenario not in scenarios(release): + pytest.skip(f"{release} has no {scenario}") + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + released = _immutable_rows(release, scenario) + shown = frozen.cli("workflow", "lineage", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + expected = [] + for version, row in enumerate(released, 1): + digest = json.loads(row["manifest_json"])["spec_sha256"] + origin = "planner" if scenario == "completed" else ("file" if version == 1 else "revision") + parent = "-" if version == 1 else released[0]["workflow_id"] + marker = "*" if row["workflow_id"] == frozen.workflow_id else " " + expected.append((marker, f"v{version}", row["workflow_id"], origin, row["state"], + f"parent={parent}", f"digest={digest}")) + assert [tuple(line.split()) if line.startswith("*") else (" ", *line.split()) + for line in shown.output.splitlines() if line.strip()] == expected + + +def _store(frozen: Frozen): + from syncmill.workflow import WorkflowStore + + return WorkflowStore(frozen.runs_root, frozen.facts["project_id"]) + + +def test_events_read_as_the_release_wrote_them(release, tmp_path, monkeypatch): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + db = FROZEN / release / "completed" / "workflows" / "workflows.db" + conn = sqlite3.connect(f"file:{db}?mode=ro&immutable=1", uri=True) + try: + written = conn.execute( + "SELECT seq, event_type, node_id, payload_json FROM workflow_events " + "WHERE workflow_id = ? ORDER BY seq", (frozen.workflow_id,) + ).fetchall() + finally: + conn.close() + assert len(written) >= 2 + events = _store(frozen).events(frozen.workflow_id) + assert [(e["seq"], e["event_type"], e["node_id"], e["payload"]) for e in events] == [ + (seq, kind, node, json.loads(payload)) for seq, kind, node, payload in written + ] + middle = written[len(written) // 2][0] + assert [e["seq"] for e in _store(frozen).events(frozen.workflow_id, after=middle)] == [ + seq for seq, *_ in written if seq > middle + ] + + +@pytest.mark.parametrize("scenario", ["completed", "no-changes", "gate-failed", "running"]) +def test_released_node_bundles_verify(release, scenario, tmp_path, monkeypatch): + import hashlib + + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + nodes = json.loads(frozen.row()["manifest_json"])["nodes"] + store = _store(frozen) + verified = [] + for node_id, node in nodes.items(): + if node["bundle_sha256"] is None: + continue + record, result, patch = store.read_node_bundle( + frozen.workflow_id, node_id, node["output_path"], node["patch_path"] + ) + base = f"workflows/{frozen.workflow_id}" + assert result == frozen.frozen_bytes(f"{base}/{node['output_path']}") + assert patch == frozen.frozen_bytes(f"{base}/{node['patch_path']}") + commit = json.loads(frozen.frozen_bytes( + f"{base}/{Path(node['output_path']).parent}/commit.json" + )) + assert commit["schema_version"] == 3 + assert commit["bundle_sha256"] == node["bundle_sha256"] == hashlib.sha256( + result + b"\0" + patch + ).hexdigest() + assert (record["node_id"], record["workflow_id"]) == (node_id, frozen.workflow_id) + verified.append(node_id) + # Every finished node left a bundle: both for a finished run, only the + # dependency for a run that died or failed in the final node. + assert verified == {"running": ["draft"]}.get(scenario, ["draft", "fix"]) + + +def test_logs_show_the_released_stream_logs(release, tmp_path, monkeypatch): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + shown = frozen.cli("workflow", "logs", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + expected = [] + for node in ("draft", "fix"): + log = frozen.frozen_bytes(f"workflows/{frozen.workflow_id}/nodes/{node}/stream.log") + expected += [f"== {node} ==", *log.decode().splitlines()] + assert [line for line in shown.stdout.splitlines() if line] == [l for l in expected if l] + + +def test_cancel_a_released_planned_workflow(release, tmp_path, monkeypatch): + frozen = Frozen(release, "planned", tmp_path, monkeypatch) + cancelled = frozen.cli("workflow", "cancel", frozen.workflow_id) + assert cancelled.exit_code == 0, cancelled.output + assert frozen.row()["state"] == "cancelled" + + +class _Later: + """A clock long after every fixture was written.""" + + @staticmethod + def install(monkeypatch): + from datetime import UTC, datetime + + class Later(datetime): + @classmethod + def now(cls, tz=None): + return datetime(2100, 1, 1, tzinfo=tz or UTC) + + monkeypatch.setattr("syncmill.workflow.datetime", Later) + + +@pytest.mark.parametrize( + ("scenario", "pruned"), + [("completed", True), ("failed", True), ("cancelled", True), ("planned", False), + ("recovery-required", False)], +) +def test_prune_removes_only_released_terminal_workflows( + release, scenario, pruned, tmp_path, monkeypatch +): + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + _Later.install(monkeypatch) + result = frozen.cli("workflow", "prune", "--keep", "0", "--retention-days", "1") + assert result.exit_code == 0, result.output + remaining = [r["workflow_id"] for r in frozen.rows("SELECT workflow_id FROM workflows")] + directory = frozen.project_dir / "workflows" / frozen.workflow_id + if pruned: + assert remaining == [] and not directory.exists() + else: + assert remaining == [frozen.workflow_id] and (directory / "spec.json").is_file() + + +# ── the workflow-origin run artifact ────────────────────────────────────── + + +def _final(frozen: Frozen) -> tuple[str, Path]: + [run_id] = frozen.facts["run_artifacts"] + return run_id, frozen.project_dir / "runs" / run_id + + +def test_diff_and_show_read_a_released_final_artifact(release, tmp_path, monkeypatch): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + assert run_id == frozen.row()["final_run_id"] == f"{frozen.workflow_id}-final" + diff = frozen.cli("diff", run_id) + assert diff.exit_code == 0, diff.output + assert diff.stdout == (run_dir / "winner.patch").read_text() + shown = frozen.cli("runs", "show", run_id) + assert shown.exit_code == 0, shown.output + manifest = json.loads((run_dir / "manifest.json").read_text()) + plan = manifest["plan"] + assert (plan["origin"], plan["workflow_id"], plan["node_id"]) == ( + "workflow", frozen.workflow_id, "fix" + ) + # v0.3.0 records the final node's agent; v0.2.0 did not. + assert plan.get("final_agent") == (None if release == "v0.2.0" else "kimi-code") + + +@pytest.mark.parametrize("prune_first", [False, True]) +def test_apply_a_released_approved_final_artifact( + release, prune_first, tmp_path, monkeypatch +): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + run_id, _ = _final(frozen) + if prune_first: + # Its approval is on the artifact: losing the workflow row is fine. + _Later.install(monkeypatch) + assert frozen.cli("workflow", "prune", "--keep", "0", "--retention-days", "1").exit_code == 0 + assert frozen.rows("SELECT * FROM workflows") == [] + applied = frozen.cli("apply", run_id) + assert applied.exit_code == 0, applied.output + assert (frozen.repo / "calc.py").read_text() == "def add(a, b):\n return a + b\n" + + +def test_apply_heals_a_released_approval_the_crash_never_wrote(release, tmp_path, monkeypatch): + frozen = Frozen(release, "unapproved-completed", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + assert "approved" not in json.loads((run_dir / "manifest.json").read_text())["plan"] + applied = frozen.cli("apply", run_id) + assert applied.exit_code == 0, applied.output + assert (frozen.repo / "calc.py").read_text() == "def add(a, b):\n return a + b\n" + assert "approved" in json.loads((run_dir / "manifest.json").read_text())["plan"] + + +def test_apply_refuses_a_released_final_artifact_its_workflow_never_completed( + release, tmp_path, monkeypatch +): + frozen = Frozen(release, "unapproved-running", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + before = (run_dir / "manifest.json").read_bytes() + refused = frozen.cli("apply", run_id) + assert refused.exit_code == 1 + assert refused.output.splitlines()[-1] == ( + f"Error: run {run_id} came from workflow {frozen.workflow_id}, which is " + "'running', not completed. Its result is not approved for apply." + ) + assert (run_dir / "manifest.json").read_bytes() == before + assert (frozen.repo / "calc.py").read_text() == frozen.bindings["tree"]["calc.py"] + + +def test_apply_of_a_released_no_changes_artifact(release, tmp_path, monkeypatch): + frozen = Frozen(release, "no-changes", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + assert json.loads((run_dir / "manifest.json").read_text())["state"] == "no_changes" + assert (run_dir / "winner.patch").read_bytes() == b"" + result = frozen.cli("apply", run_id) + assert (result.exit_code, result.output.splitlines()[-1]) == ( + 1, f"Error: run {run_id} is 'no_changes', not pending" + ) + + +def test_recover_skips_a_released_final_artifact(release, tmp_path, monkeypatch): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + before = (run_dir / "manifest.json").read_bytes() + recovered = frozen.cli("recover", run_id) + assert recovered.exit_code == 0, recovered.output + assert recovered.output.splitlines() == [ + f"Run {run_id}: skipped \N{EM DASH} run is 'pending', not applying" + ] + assert (run_dir / "manifest.json").read_bytes() == before From 7710662205f1a2e4ed7136cef31ce7d9cbc5fa68 Mon Sep 17 00:00:00 2001 From: memtomem <118508877+memtomem@users.noreply.github.com> Date: Mon, 9 Nov 2026 19:57:28 +0900 Subject: [PATCH 2/4] Make the released-workflow readers fail for the regressions they name (#271) Review round 1 found checks that passed with the guarded code removed: - bundle verification: damaged patches, results and commit records (version and digest) are refused, and a patch re-sealed in commit.json and the database still fails result.json's own patch digest; - runs show: its JSON is compared with the released manifest; - usage: the show line and the loaded record match the released manifest. Twelve injected regressions now each fail an intended assertion. Co-authored-by: Claude Opus 5.5 --- tests/test_frozen_workflows.py | 87 ++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/tests/test_frozen_workflows.py b/tests/test_frozen_workflows.py index 4565e31..ddda19c 100644 --- a/tests/test_frozen_workflows.py +++ b/tests/test_frozen_workflows.py @@ -337,6 +337,19 @@ def test_list_and_show_read_a_released_workflow(release, scenario, tmp_path, mon assert _lines(text, "workflow: ") == [f"workflow: {frozen.workflow_id}"] assert _lines(text, "state: ") == [f"state: {expected_state}"] assert _lines(text, "calls_used: ") == [f"calls_used: {manifest['calls_used']}"] + usage = manifest["usage"] + assert _lines(text, "usage: ") == [ + f"usage: input={usage['input_tokens']} output={usage['output_tokens']} " + f"cost_usd={usage['total_cost_usd']:.6f}" + ] + loaded = _store(frozen).load(frozen.workflow_id) + assert (loaded.input_tokens, loaded.output_tokens, loaded.total_cost_usd) == ( + usage["input_tokens"], usage["output_tokens"], usage["total_cost_usd"] + ) + if scenario not in ("created", "revised"): + # The planner (and any node that ran) reported tokens: zero would + # not tell a dropped value from a recorded one. + assert usage["input_tokens"] > 0 assert _lines(text, "plan_origin: ") == [f"plan_origin: {origin}"] assert _lines(text, "goal: ") == [f"goal: {json.loads(row['spec_json'])['goal']}"] final = row["final_run_id"] @@ -546,6 +559,79 @@ def test_released_node_bundles_verify(release, scenario, tmp_path, monkeypatch): assert verified == {"running": ["draft"]}.get(scenario, ["draft", "fix"]) +def _corrupt_patch(bundle: Path) -> None: + patch = bundle / "winner.patch" + patch.write_bytes(patch.read_bytes() + b"\n") + + +def _corrupt_result(bundle: Path) -> None: + result = bundle / "result.json" + result.write_bytes(result.read_bytes().replace(b'"exit_code": 0', b'"exit_code": 1', 1)) + + +def _corrupt_commit_version(bundle: Path) -> None: + commit = bundle / "commit.json" + record = json.loads(commit.read_text()) + commit.write_text(json.dumps({**record, "schema_version": 2})) + + +def _corrupt_commit_digest(bundle: Path) -> None: + commit = bundle / "commit.json" + record = json.loads(commit.read_text()) + commit.write_text(json.dumps({**record, "bundle_sha256": "0" * 64})) + + +@pytest.mark.parametrize( + "damage", [_corrupt_patch, _corrupt_result, _corrupt_commit_version, _corrupt_commit_digest] +) +def test_a_damaged_released_bundle_is_refused(release, damage, tmp_path, monkeypatch): + from syncmill.workflow import WorkflowError + + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + node = json.loads(frozen.row()["manifest_json"])["nodes"]["draft"] + bundle = frozen.project_dir / "workflows" / frozen.workflow_id / Path(node["output_path"]).parent + before = (bundle / "result.json").read_bytes() + damage(bundle) + assert (bundle / "result.json").read_bytes() != before or damage is not _corrupt_result + with pytest.raises(WorkflowError, match="missing or corrupt bundle"): + _store(frozen).read_node_bundle( + frozen.workflow_id, "draft", node["output_path"], node["patch_path"] + ) + + +def test_a_released_patch_that_disagrees_with_its_result_is_refused( + release, tmp_path, monkeypatch +): + # The patch is altered and the commit record and the database re-sealed + # around it, so only result.json's own patch digest still disagrees. + import hashlib + + from syncmill.workflow import WorkflowError + + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + row = frozen.row() + manifest = json.loads(row["manifest_json"]) + node = manifest["nodes"]["draft"] + bundle = frozen.project_dir / "workflows" / frozen.workflow_id / Path(node["output_path"]).parent + patch = (bundle / "winner.patch").read_bytes() + b"\n" + (bundle / "winner.patch").write_bytes(patch) + sealed = hashlib.sha256((bundle / "result.json").read_bytes() + b"\0" + patch).hexdigest() + commit = json.loads((bundle / "commit.json").read_text()) + (bundle / "commit.json").write_text(json.dumps({**commit, "bundle_sha256": sealed})) + node["bundle_sha256"] = sealed + conn = sqlite3.connect(frozen.project_dir / "workflows" / "workflows.db") + try: + conn.execute("UPDATE workflows SET manifest_json = ? WHERE workflow_id = ?", + (json.dumps(manifest), frozen.workflow_id)) + conn.commit() + finally: + conn.close() + with pytest.raises(WorkflowError, match="missing or corrupt bundle"): + _store(frozen).read_node_bundle( + frozen.workflow_id, "draft", node["output_path"], node["patch_path"] + ) + + def test_logs_show_the_released_stream_logs(release, tmp_path, monkeypatch): frozen = Frozen(release, "completed", tmp_path, monkeypatch) shown = frozen.cli("workflow", "logs", frozen.workflow_id) @@ -617,6 +703,7 @@ def test_diff_and_show_read_a_released_final_artifact(release, tmp_path, monkeyp shown = frozen.cli("runs", "show", run_id) assert shown.exit_code == 0, shown.output manifest = json.loads((run_dir / "manifest.json").read_text()) + assert json.loads(shown.stdout) == manifest plan = manifest["plan"] assert (plan["origin"], plan["workflow_id"], plan["node_id"]) == ( "workflow", frozen.workflow_id, "fix" From 698f02a307efe1d82cae46aeb664d445615e32f6 Mon Sep 17 00:00:00 2001 From: memtomem <118508877+memtomem@users.noreply.github.com> Date: Mon, 9 Nov 2026 20:14:56 +0900 Subject: [PATCH 3/4] Refuse a released bundle the database no longer vouches for (#271) Review round 2: only the database record of the bundle digest is changed, the bundle files are intact, and read_node_bundle must still refuse it. Co-authored-by: Claude Opus 5.5 --- tests/test_frozen_workflows.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_frozen_workflows.py b/tests/test_frozen_workflows.py index ddda19c..0120657 100644 --- a/tests/test_frozen_workflows.py +++ b/tests/test_frozen_workflows.py @@ -632,6 +632,30 @@ def test_a_released_patch_that_disagrees_with_its_result_is_refused( ) +def test_a_released_bundle_the_database_no_longer_vouches_for_is_refused( + release, tmp_path, monkeypatch +): + # The bundle files are intact; only the database's record of the bundle + # digest changed. + from syncmill.workflow import WorkflowError + + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + manifest = json.loads(frozen.row()["manifest_json"]) + node = manifest["nodes"]["draft"] + node["bundle_sha256"] = "0" * 64 + conn = sqlite3.connect(frozen.project_dir / "workflows" / "workflows.db") + try: + conn.execute("UPDATE workflows SET manifest_json = ? WHERE workflow_id = ?", + (json.dumps(manifest), frozen.workflow_id)) + conn.commit() + finally: + conn.close() + with pytest.raises(WorkflowError, match="missing or corrupt bundle"): + _store(frozen).read_node_bundle( + frozen.workflow_id, "draft", node["output_path"], node["patch_path"] + ) + + def test_logs_show_the_released_stream_logs(release, tmp_path, monkeypatch): frozen = Frozen(release, "completed", tmp_path, monkeypatch) shown = frozen.cli("workflow", "logs", frozen.workflow_id) From 6db8fa9b910637fcadd1d3c7bdbf07bad746ee67 Mon Sep 17 00:00:00 2001 From: memtomem <118508877+memtomem@users.noreply.github.com> Date: Mon, 9 Nov 2026 20:45:16 +0900 Subject: [PATCH 4/4] Satisfy ruff in the frozen workflow tests (#271) An f-string without placeholders and a one-letter loop name failed the lint job. Co-authored-by: Claude Opus 5.5 --- tests/test_frozen_workflows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_frozen_workflows.py b/tests/test_frozen_workflows.py index 0120657..81ce351 100644 --- a/tests/test_frozen_workflows.py +++ b/tests/test_frozen_workflows.py @@ -460,7 +460,7 @@ def test_the_stored_spec_reads_back_with_its_digest(release, scenario, tmp_path, assert prompts.exit_code == 0, prompts.output lines = prompts.output.splitlines() for node in spec.nodes: - at = lines.index(f" prompt:", lines.index(next( + at = lines.index(" prompt:", lines.index(next( line for line in lines if line.startswith(f" {node.id}: ") ))) assert lines[at + 1] == f" {node.prompt}" @@ -664,7 +664,7 @@ def test_logs_show_the_released_stream_logs(release, tmp_path, monkeypatch): for node in ("draft", "fix"): log = frozen.frozen_bytes(f"workflows/{frozen.workflow_id}/nodes/{node}/stream.log") expected += [f"== {node} ==", *log.decode().splitlines()] - assert [line for line in shown.stdout.splitlines() if line] == [l for l in expected if l] + assert [line for line in shown.stdout.splitlines() if line] == [line for line in expected if line] def test_cancel_a_released_planned_workflow(release, tmp_path, monkeypatch):