diff --git a/docs/architecture/persisted-contracts.md b/docs/architecture/persisted-contracts.md index 594ea8d..fc8a5b3 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 | 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`. HEAD execution (`WorkflowCoordinator.run`, what the job worker calls): `run` of released planned, created and revised rows (accounting, stored prompts, dependency output); `resume` of released crashes and failures against the stored deadline (expired: fails before dispatch; before it: the completed dependency's bundle is reused and the unfinished node's attempt refunded, a published final artifact is reused and approved without backfilling `final_agent`, an exhausted node dispatches nothing), refused from a final or unstarted state and while the owner lives, with the released execution history and events kept; `workflow revise` (a v0.2.0 parent gains a lineage); the launch's team peek and state check; `apply --verify` narrowing (v0.2.0: the gate's list only, with a notice; also after a prune); control plans enabled refuse a released workflow planned without one. Not yet: control-plan (toolgraph) workflows written by a release, a node bundle saved just before a crash (reclaimed on resume), recovery of an interrupted workflow-origin apply | `test_frozen_workflows.py` (v0.2.0, v0.3.0), partly | +| 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`. HEAD execution (`WorkflowCoordinator.run`, what the job worker calls): `run` of released planned, created and revised rows (accounting, stored prompts, dependency output); `resume` of released crashes and failures against the stored deadline (expired: fails before dispatch; before it: the completed dependency's bundle is reused and the unfinished node's attempt refunded, a published final artifact is reused and approved without backfilling `final_agent`, an exhausted node dispatches nothing), refused from a final or unstarted state and while the owner lives, with the released execution history and events kept; `workflow revise` (a v0.2.0 parent gains a lineage); the launch's team peek and state check; `apply --verify` narrowing (v0.2.0: the gate's list only, with a notice; also after a prune); control plans enabled refuse a released workflow planned without one. Written later by the same releases (`freeze.py --part 2b`, `bindings-2b.json`): a node bundle saved just before the crash, reclaimed on resume through `read_node_result` / `read_node_bundle` with its usage added once (`_record_usage`), and re-run when its patch, its commit record or its digest disagrees; control-plan workflows, whose released `control-plan.json` is the control preflight's request, whose binding and validation topology refuse a changed configuration, and whose stored limits and node timeouts bound a run with control plans on, off, or Toolgraph off (`_execution_limits`), plus `show`, `read_control_plan`, `conversations show` and `apply` of a completed one; an interrupted apply of a final artifact, classified by `recover` (completed, not applied, manual recovery; `_bound_receipt`) without touching its approval or the workflow row. Not covered: the TUI's `_read_artifact_result`; no reader revalidates a released execution's `control-preflight.json` | `test_frozen_workflows.py` (v0.2.0, v0.3.0) | | 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 index 8cd7f57..9edaaf4 100644 --- a/tests/fixtures/persisted/workflows/freeze.py +++ b/tests/fixtures/persisted/workflows/freeze.py @@ -32,6 +32,23 @@ 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. + +`--part 2b` adds scenarios to an existing release directory without writing +anything already in it: each new scenario directory and `bindings-2b.json` +must not exist. Its scenarios, from a fresh box of their own: +- `bundle-saved`: the worker exits right after the final node's bundle is + saved, before the manifest records the node's outcome; then + `bundle-saved-recovered`, after the release's own `status`. The fake kimi + reports token usage in these, so the bundle carries some. +- `control-planned`, `control-completed`: with Toolgraph control plans on and + a fake `toolgraph` that answers both of its protocols from the request. +- `apply-unreceipted`, `apply-receipted`: a completed workflow's final + artifact applied in the release interpreter, dying right after the + `applying` record or right before the `applied` one (the artifacts family's + crash script). + + python tests/fixtures/persisted/workflows/freeze.py --part 2b \\ + --release-root /tmp/wt-v0.3.0 --out tests/fixtures/persisted/workflows/v0.3.0 """ from __future__ import annotations @@ -102,6 +119,9 @@ {"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"}, ] + if control.get("kimi_usage"): + events.append({"role": "meta", "type": "usage", + "usage": {"input_tokens": 7, "output_tokens": 3}}) for event in events: print(json.dumps(event)) ''' @@ -141,10 +161,98 @@ def save(self, *args, **kwargs): os._exit(0) return saved ArtifactStore.save = save + elif point == "bundle": + from syncmill.workflow import WorkflowStore + + original_save = WorkflowStore.save_node_result + + def save_node_result(self, workflow_id, node_id, *args, **kwargs): + saved = original_save(self, workflow_id, node_id, *args, **kwargs) + if node_id == "fix": + os._exit(0) # bundle on disk, the node's outcome never recorded + return saved + WorkflowStore.save_node_result = save_node_result _install() ''' +# Both Toolgraph protocols (`preflight.py` is identical in v0.2.0, v0.3.0 and +# HEAD), each answered from the request it gets, never a fixed golden. +FAKE_TOOLGRAPH = '''\ +import hashlib, json, sys +from pathlib import Path +args = sys.argv[1:] +out = Path(args[args.index("--out") + 1]) +profile = args[args.index("--profile") + 1] +if args[0] == "control-preflight": + raw = Path(args[1]).read_bytes() + plan = json.loads(raw) + doc = { + "schema_version": 1, "kind": "toolgraph.control-preflight", + "run_id": plan["run_id"], "created_at": "2026-11-10T00:00:00+00:00", + "mode": "advisory", "profile": profile, + "plan_digest": "sha256:" + hashlib.sha256(raw).hexdigest(), + "graph_state": {"instance_id": "frozen-graph", "generation": 7}, + "decision": "advisory_allow", + "evaluations": [ + {"principal": row["id"], "agent_found": True, + "eligible": list(row["candidate_tools"]), "rejected": []} + for row in plan["principals"] + ], + "findings": [], + } +elif args[0] == "preflight": + agent, tools = args[1], args[2:args.index("--profile")] + doc = { + "schema_version": 1, "kind": "toolgraph.preflight", + "run_id": args[args.index("--run-id") + 1], + "created_at": "2026-11-10T00:00:00+00:00", "graph_generation": 7, + "agent": agent, "agent_found": True, "profile": profile, "mode": "advisory", + "eligible": list(tools), "rejected": [], "features": [], + "decision": "advisory_allow", + } +else: + sys.exit("fake toolgraph: unknown command " + repr(args[:1])) +out.write_text(json.dumps(doc), encoding="utf-8") +''' +TOOLGRAPH_ENV = { + "SYNCMILL_INTEGRATION__TOOLGRAPH__ENABLED": "true", + "SYNCMILL_INTEGRATION__TOOLGRAPH__CONTROL_PLAN_ENABLED": "true", + "SYNCMILL_INTEGRATION__TOOLGRAPH__CANDIDATE_TOOLS": json.dumps( + ["workspace::read", "workspace::write"] + ), + "SYNCMILL_INTEGRATION__TOOLGRAPH__AGENT_MAP": json.dumps( + {"codex": "codex", "kimi-code": "kimi"} + ), +} + +# The artifacts family's crash script: runs inside the released interpreter, +# applies, then dies at one manifest write. +CRASH_APPLY = '''\ +import os, sys +from pathlib import Path +from syncmill.artifacts import ArtifactStore +from syncmill.worktree import repository_fingerprint +runs_root, project, run_id, repo, die_at = sys.argv[1:6] +original = ArtifactStore._compare_and_swap_manifest +def die(self, run_dir, expected, replacement): + state = replacement.get("state") + if die_at == "before-applied" and state == "applied": + os._exit(0) # receipt written, patch in the source, flip never happens + original(self, run_dir, expected, replacement) + if die_at == "after-applying" and state == "applying": + os._exit(0) # applying recorded, source not yet touched, no receipt +ArtifactStore._compare_and_swap_manifest = die +store = ArtifactStore(Path(runs_root), project) +store.apply(run_id, Path(repo), repository_fingerprint(Path(repo))) +sys.exit("apply returned; the crash hook never fired") +''' + +PART_2B = ( + "bundle-saved", "bundle-saved-recovered", "control-planned", "control-completed", + "apply-unreceipted", "apply-receipted", +) + HOOK_MODULE = "_syncmill_freeze_hook" FROZEN_HOST = "frozen-host" @@ -191,17 +299,27 @@ def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--release-root", type=Path, required=True) parser.add_argument("--out", type=Path, required=True) + parser.add_argument("--part", choices=("1", "2b"), default="1") 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() + if args.part == "1": + if out.exists(): + sys.exit(f"{out} exists; frozen fixtures are never regenerated") + else: + # Additive: the release directory exists, and nothing this part + # writes may be there already. + if json.loads((out / "bindings.json").read_text())["release"] != version: + sys.exit(f"{out} holds another release's fixtures") + taken = [name for name in (*PART_2B, "bindings-2b.json") if (out / name).exists()] + if taken: + sys.exit(f"{out} already has {taken}; frozen fixtures are never regenerated") 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): @@ -214,14 +332,16 @@ def main() -> None: 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) + _generate(Path(tmp).resolve(), runtime, python, syncmill, version, out, args.part) 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: +def _generate( + box: Path, runtime: Path, python: Path, syncmill: Path, version: str, out: Path, part: str +) -> None: repo, home, state, fakebin = (box / n for n in ("repo", "home", "state", "fakebin")) for folder in (repo, home, state, fakebin): folder.mkdir() @@ -230,6 +350,11 @@ def _generate(box: Path, runtime: Path, syncmill: Path, version: str, out: Path) (fakebin / binary).write_text(f"#!{sys.executable}\n" + FAKE_AGENT, encoding="utf-8") (fakebin / binary).chmod(0o755) (fakebin / "python3").symlink_to(sys.executable) + if part == "2b": + (fakebin / "toolgraph").write_text( + f"#!{sys.executable}\n" + FAKE_TOOLGRAPH, encoding="utf-8" + ) + (fakebin / "toolgraph").chmod(0o755) base_env = { "HOME": str(home), "PATH": os.pathsep.join([str(fakebin), "/usr/bin", "/bin"]), @@ -258,7 +383,7 @@ def git(*argv: str) -> None: git("add", "-A") git("commit", "-q", "-m", "frozen tree") - out.mkdir(parents=True) + out.mkdir(parents=True, exist_ok=part == "2b") bindings: dict = { "release": version, "box_root": str(box), @@ -269,10 +394,11 @@ def git(*argv: str) -> None: } class Scenario: - def __init__(self, name: str, *, gate: bool = True, **control: str): + def __init__(self, name: str, *, gate: bool = True, env: dict | None = None, + **control): self.name = name self.project = f"frozen-workflows-{name}" - self.env = {**base_env, "SYNCMILL_PROJECT__ID": self.project} + self.env = {**base_env, **(env or {}), "SYNCMILL_PROJECT__ID": self.project} if gate: self.env["SYNCMILL_GATE__COMMANDS"] = json.dumps(GATE) self.control = {"spec": SPEC, **control} @@ -392,6 +518,11 @@ def run_until(sc: Scenario, workflow_id: str, marker: str) -> None: sys.exit(f"{sc.name}: the worker or its agent survived SIGKILL") time.sleep(0.1) + if part == "2b": + _part_2b(Scenario, freeze, git, home, state, repo, python) + (out / "bindings-2b.json").write_text(json.dumps(bindings, indent=2) + "\n") + return + # ── the lifecycle ──────────────────────────────────────────────────── sc = Scenario("planned") sc.plan() @@ -469,5 +600,44 @@ def run_until(sc: Scenario, workflow_id: str, marker: str) -> None: (out / "bindings.json").write_text(json.dumps(bindings, indent=2) + "\n") +def _part_2b(Scenario, freeze, git, home: Path, state: Path, repo: Path, python: Path) -> None: + def crashed_run(sc, workflow_id: str, point: str) -> None: + sc.prepare(crash_at=point) + sc.workflow("run", workflow_id, ok=False) + (home / "crash-at").unlink() + + # The worker exits right after the final node's bundle is saved; then + # the release's own `status` recovers the dead lease. + sc = Scenario("bundle-saved", kimi_usage=True) + workflow_id = sc.plan() + crashed_run(sc, workflow_id, "bundle") + freeze(sc) + sc.name = "bundle-saved-recovered" + sc.workflow("status", workflow_id) + freeze(sc) + + sc = Scenario("control-planned", env=TOOLGRAPH_ENV) + sc.plan() + freeze(sc) + + sc = Scenario("control-completed", env=TOOLGRAPH_ENV) + workflow_id = sc.plan() + sc.workflow("run", workflow_id) + freeze(sc) + + for name, die_at in (("apply-unreceipted", "after-applying"), + ("apply-receipted", "before-applied")): + sc = Scenario(name) + workflow_id = sc.plan() + sc.workflow("run", workflow_id) + subprocess.run( + [str(python), "-c", CRASH_APPLY, str(state / "runs"), sc.project, + f"{workflow_id}-final", str(repo), die_at], + cwd=repo, env=sc.env, check=True, + ) + freeze(sc) + git("checkout", "--", ".") + + if __name__ == "__main__": main() diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/applications/20261109T213723644855Z.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/applications/20261109T213723644855Z.json new file mode 100644 index 0000000..0e44e7a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/applications/20261109T213723644855Z.json @@ -0,0 +1,7 @@ +{ + "applied_at": "2026-11-09T21:37:23.644881+00:00", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpnhjq8lqv/repo", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "apply_id": "94ef2849-5ce2-40cd-b87c-284c34be0fc5", + "run_id": "wf-15778afc04544c85-final" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/manifest.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/manifest.json new file mode 100644 index 0000000..4800912 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/manifest.json @@ -0,0 +1,44 @@ +{ + "schema_version": 1, + "run_id": "wf-15778afc04544c85-final", + "project_id": "frozen-workflows-apply-receipted", + "parent_run_id": null, + "created_at": "2026-11-09T21:37:21.321318+00:00", + "updated_at": "2026-11-09T21:37:23.607822+00:00", + "state": "applying", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpnhjq8lqv/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "7e90c6990ae7e08ea53f3d9c8a2b5ca8f03f0452", + "task_id": "4c27a7b7-0a62-4bd2-9359-638df1bb18d2", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-15778afc04544c85", + "node_id": "fix", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "0bd653ba0ada6e52667e0096e9cc24baa8b1f3c8f389b464449bde8e66820a13", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved": { + "workflow_id": "wf-15778afc04544c85", + "run_id": "wf-15778afc04544c85-final", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved_at": "2026-11-09T21:37:21.332020+00:00" + } + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null, + "apply_id": "94ef2849-5ce2-40cd-b87c-284c34be0fc5", + "apply_owner": "15589-7fe8fa59", + "apply_started_at": "2026-11-09T21:37:23.607711+00:00" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/result.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-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.2.0/apply-receipted/runs/wf-15778afc04544c85-final/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/runs/wf-15778afc04544c85-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.2.0/apply-receipted/workflows/.locks/wf-15778afc04544c85.lock b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/.locks/wf-15778afc04544c85.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-1/commit.json new file mode 100644 index 0000000..a7485cd --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-15778afc04544c85", "node_id": "draft", "lease_id": "cd158718c45243ebbe0deb1b4562feb8", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "04439868670029d93d31e925c5c046bb6d94202e3b1042585d3cedf16433388b"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-1/result.json new file mode 100644 index 0000000..352a33e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-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-15778afc04544c85", + "node_id": "draft", + "lease_id": "cd158718c45243ebbe0deb1b4562feb8", + "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/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/cd158718c45243ebbe0deb1b4562feb8-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/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/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/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-1/commit.json new file mode 100644 index 0000000..6055da7 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-15778afc04544c85", "node_id": "fix", "lease_id": "cd158718c45243ebbe0deb1b4562feb8", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "0bd653ba0ada6e52667e0096e9cc24baa8b1f3c8f389b464449bde8e66820a13"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-1/result.json new file mode 100644 index 0000000..1b88c07 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-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-15778afc04544c85", + "node_id": "fix", + "lease_id": "cd158718c45243ebbe0deb1b4562feb8", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/cd158718c45243ebbe0deb1b4562feb8-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/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/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.2.0/apply-receipted/workflows/wf-15778afc04544c85/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/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/apply-receipted/workflows/wf-15778afc04544c85/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/wf-15778afc04544c85/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/apply-receipted/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/workflows.db new file mode 100644 index 0000000..d3c5ac0 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/apply-receipted/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-final/manifest.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-final/manifest.json new file mode 100644 index 0000000..5308aa8 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-final/manifest.json @@ -0,0 +1,44 @@ +{ + "schema_version": 1, + "run_id": "wf-4e8e7fcd823e4b05-final", + "project_id": "frozen-workflows-apply-unreceipted", + "parent_run_id": null, + "created_at": "2026-11-09T21:37:17.273174+00:00", + "updated_at": "2026-11-09T21:37:19.570582+00:00", + "state": "applying", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpnhjq8lqv/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "9bc4a90142597829c608eaca9e5110762061af3c", + "task_id": "e8f71e46-054f-4165-b253-153c412bfcef", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-4e8e7fcd823e4b05", + "node_id": "fix", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "afe51e99be8cbfc123b85df87fbd4a84e6421158ec9574d90ddf5611d9add144", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved": { + "workflow_id": "wf-4e8e7fcd823e4b05", + "run_id": "wf-4e8e7fcd823e4b05-final", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved_at": "2026-11-09T21:37:17.284007+00:00" + } + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null, + "apply_id": "64eed5cb-2863-45d3-bd36-fdf00f49f8da", + "apply_owner": "15467-d99eecd5", + "apply_started_at": "2026-11-09T21:37:19.570473+00:00" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-final/result.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-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.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-final/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/runs/wf-4e8e7fcd823e4b05-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.2.0/apply-unreceipted/workflows/.locks/wf-4e8e7fcd823e4b05.lock b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/.locks/wf-4e8e7fcd823e4b05.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-1/commit.json new file mode 100644 index 0000000..309389a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-4e8e7fcd823e4b05", "node_id": "draft", "lease_id": "806172f1ce0b4475ba7c8c01844b21d0", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "83194c232e994465db92e0214b26de925dfaf78fca9946290ab8cc0fefde05dd"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-1/result.json new file mode 100644 index 0000000..8bf2574 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-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-4e8e7fcd823e4b05", + "node_id": "draft", + "lease_id": "806172f1ce0b4475ba7c8c01844b21d0", + "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/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/806172f1ce0b4475ba7c8c01844b21d0-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/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/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/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-1/commit.json new file mode 100644 index 0000000..10b0143 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-4e8e7fcd823e4b05", "node_id": "fix", "lease_id": "806172f1ce0b4475ba7c8c01844b21d0", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "afe51e99be8cbfc123b85df87fbd4a84e6421158ec9574d90ddf5611d9add144"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-1/result.json new file mode 100644 index 0000000..459217d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-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-4e8e7fcd823e4b05", + "node_id": "fix", + "lease_id": "806172f1ce0b4475ba7c8c01844b21d0", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/806172f1ce0b4475ba7c8c01844b21d0-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/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/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.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/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/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/wf-4e8e7fcd823e4b05/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/apply-unreceipted/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/workflows.db new file mode 100644 index 0000000..8ff6bbe Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/apply-unreceipted/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bindings-2b.json b/tests/fixtures/persisted/workflows/v0.2.0/bindings-2b.json new file mode 100644 index 0000000..8f18036 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bindings-2b.json @@ -0,0 +1,140 @@ +{ + "release": "0.2.0", + "box_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpnhjq8lqv", + "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": { + "bundle-saved": { + "project_id": "frozen-workflows-bundle-saved", + "workflow_ids": [ + "wf-bbcc5e3cb536418e" + ], + "states": { + "wf-bbcc5e3cb536418e": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "15137@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "bundle-saved-recovered": { + "project_id": "frozen-workflows-bundle-saved", + "workflow_ids": [ + "wf-bbcc5e3cb536418e" + ], + "states": { + "wf-bbcc5e3cb536418e": "recovery_required" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "control-planned": { + "project_id": "frozen-workflows-control-planned", + "workflow_ids": [ + "wf-6494f914db7441a1" + ], + "states": { + "wf-6494f914db7441a1": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "control-completed": { + "project_id": "frozen-workflows-control-completed", + "workflow_ids": [ + "wf-6c587c6f51e941d8" + ], + "states": { + "wf-6c587c6f51e941d8": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-6c587c6f51e941d8-final" + ], + "run_artifacts": [ + "wf-6c587c6f51e941d8-final" + ], + "wal": [] + }, + "apply-unreceipted": { + "project_id": "frozen-workflows-apply-unreceipted", + "workflow_ids": [ + "wf-4e8e7fcd823e4b05" + ], + "states": { + "wf-4e8e7fcd823e4b05": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-4e8e7fcd823e4b05-final" + ], + "run_artifacts": [ + "wf-4e8e7fcd823e4b05-final" + ], + "wal": [] + }, + "apply-receipted": { + "project_id": "frozen-workflows-apply-receipted", + "workflow_ids": [ + "wf-15778afc04544c85" + ], + "states": { + "wf-15778afc04544c85": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-15778afc04544c85-final" + ], + "run_artifacts": [ + "wf-15778afc04544c85-final" + ], + "wal": [] + } + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/.locks/wf-bbcc5e3cb536418e.lock b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/.locks/wf-bbcc5e3cb536418e.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json new file mode 100644 index 0000000..540a358 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-bbcc5e3cb536418e", "node_id": "draft", "lease_id": "6322ef0c854f4ef4bff247c3ec2667c0", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "2a9b347efb9398f3b583e83f4cbab96fd2c1d5abb00edcb265ea73c04f341237"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json new file mode 100644 index 0000000..cdcf72a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-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-bbcc5e3cb536418e", + "node_id": "draft", + "lease_id": "6322ef0c854f4ef4bff247c3ec2667c0", + "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/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-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/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/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/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json new file mode 100644 index 0000000..080cdaa --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-bbcc5e3cb536418e", "node_id": "fix", "lease_id": "6322ef0c854f4ef4bff247c3ec2667c0", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "fd85b6d8dd1d9b0fa92a03fe1a90fbfe61d21bfcceb56c89ebcfca6080ce666c"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json new file mode 100644 index 0000000..4991f19 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json @@ -0,0 +1,39 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.09, + "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-bbcc5e3cb536418e", + "node_id": "fix", + "lease_id": "6322ef0c854f4ef4bff247c3ec2667c0", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 7, + "output_tokens": 3, + "cache_read_tokens": null, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-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/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/stream.log new file mode 100644 index 0000000..e2f7891 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/nodes/fix/stream.log @@ -0,0 +1,3 @@ +{"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"} +{"role": "meta", "type": "usage", "usage": {"input_tokens": 7, "output_tokens": 3}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/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/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/wf-bbcc5e3cb536418e/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/bundle-saved-recovered/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/workflows.db new file mode 100644 index 0000000..02b36ee Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved-recovered/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/.locks/wf-bbcc5e3cb536418e.lock b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/.locks/wf-bbcc5e3cb536418e.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json new file mode 100644 index 0000000..540a358 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-bbcc5e3cb536418e", "node_id": "draft", "lease_id": "6322ef0c854f4ef4bff247c3ec2667c0", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "2a9b347efb9398f3b583e83f4cbab96fd2c1d5abb00edcb265ea73c04f341237"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json new file mode 100644 index 0000000..cdcf72a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-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-bbcc5e3cb536418e", + "node_id": "draft", + "lease_id": "6322ef0c854f4ef4bff247c3ec2667c0", + "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/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/6322ef0c854f4ef4bff247c3ec2667c0-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/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/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/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json new file mode 100644 index 0000000..080cdaa --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-bbcc5e3cb536418e", "node_id": "fix", "lease_id": "6322ef0c854f4ef4bff247c3ec2667c0", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "fd85b6d8dd1d9b0fa92a03fe1a90fbfe61d21bfcceb56c89ebcfca6080ce666c"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json new file mode 100644 index 0000000..4991f19 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/result.json @@ -0,0 +1,39 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.09, + "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-bbcc5e3cb536418e", + "node_id": "fix", + "lease_id": "6322ef0c854f4ef4bff247c3ec2667c0", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 7, + "output_tokens": 3, + "cache_read_tokens": null, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/6322ef0c854f4ef4bff247c3ec2667c0-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/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/stream.log new file mode 100644 index 0000000..e2f7891 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/nodes/fix/stream.log @@ -0,0 +1,3 @@ +{"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"} +{"role": "meta", "type": "usage", "usage": {"input_tokens": 7, "output_tokens": 3}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/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/bundle-saved/workflows/wf-bbcc5e3cb536418e/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/wf-bbcc5e3cb536418e/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/bundle-saved/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/workflows.db new file mode 100644 index 0000000..97c9d3e Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/bundle-saved/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/wf-6c587c6f51e941d8-final/manifest.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/wf-6c587c6f51e941d8-final/manifest.json new file mode 100644 index 0000000..bb99d34 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/wf-6c587c6f51e941d8-final/manifest.json @@ -0,0 +1,42 @@ +{ + "schema_version": 1, + "run_id": "wf-6c587c6f51e941d8-final", + "project_id": "frozen-workflows-control-completed", + "parent_run_id": null, + "created_at": "2026-11-09T21:37:13.405646+00:00", + "updated_at": "2026-11-09T21:37:13.415935+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpnhjq8lqv/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "ac84efac4258a31be893091b550013abd2972a80", + "task_id": "d6824576-76d1-42bf-a32b-0bd33b8ffabc", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-6c587c6f51e941d8", + "node_id": "fix", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "aac21f043fb90de2b50bd88997777cfcc18e208047346c641072e5aaeadc7653", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "control_plan_digest": "sha256:d9f2f0b88948d39b838448957615a461cf8baa5c854ed879fbd8644db6372079", + "approved": { + "workflow_id": "wf-6c587c6f51e941d8", + "run_id": "wf-6c587c6f51e941d8-final", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved_at": "2026-11-09T21:37:13.415929+00:00" + } + }, + "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.2.0/control-completed/runs/wf-6c587c6f51e941d8-final/result.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/wf-6c587c6f51e941d8-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/wf-6c587c6f51e941d8-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.2.0/control-completed/runs/wf-6c587c6f51e941d8-final/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/wf-6c587c6f51e941d8-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/runs/wf-6c587c6f51e941d8-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.2.0/control-completed/workflows/.locks/wf-6c587c6f51e941d8.lock b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/.locks/wf-6c587c6f51e941d8.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/control-plan.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/control-plan.json new file mode 100644 index 0000000..8c8bfe0 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/control-plan.json @@ -0,0 +1 @@ +{"edges":[{"decided_by":"code","from":"control:start","kind":"always","to":"workflow:draft"},{"decided_by":"code","from":"workflow:draft","kind":"success","to":"workflow:fix"},{"decided_by":"code","from":"workflow:draft","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"success","to":"control:validator:final"},{"decided_by":"code","from":"control:validator:final","kind":"success","to":"control:end"},{"decided_by":"code","from":"control:validator:final","kind":"failure","to":"control:end"}],"kind":"toolgraph.control-plan","limits":{"maximum_agent_calls":2,"maximum_cycles":0,"maximum_parallelism":2},"nodes":[{"agent_call":false,"id":"control:start","kind":"start","principals":[]},{"agent_call":true,"id":"workflow:draft","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["codex"],"timeout_seconds":300},{"agent_call":true,"id":"workflow:fix","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["kimi"],"timeout_seconds":300},{"agent_call":false,"id":"control:validator:final","kind":"validator","principals":[],"validator_scope":"workspace_result"},{"agent_call":false,"id":"control:end","kind":"end","principals":[]}],"principals":[{"candidate_tools":["workspace::read","workspace::write"],"id":"codex"},{"candidate_tools":["workspace::read","workspace::write"],"id":"kimi"}],"producer":{"name":"syncmill","version":"0.2.0"},"run_id":"wf-6c587c6f51e941d8","schema_version":1} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/executions/wfx-dcb87bb741f14738bbc1d2c5/control-plan.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/executions/wfx-dcb87bb741f14738bbc1d2c5/control-plan.json new file mode 100644 index 0000000..8c8bfe0 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/executions/wfx-dcb87bb741f14738bbc1d2c5/control-plan.json @@ -0,0 +1 @@ +{"edges":[{"decided_by":"code","from":"control:start","kind":"always","to":"workflow:draft"},{"decided_by":"code","from":"workflow:draft","kind":"success","to":"workflow:fix"},{"decided_by":"code","from":"workflow:draft","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"success","to":"control:validator:final"},{"decided_by":"code","from":"control:validator:final","kind":"success","to":"control:end"},{"decided_by":"code","from":"control:validator:final","kind":"failure","to":"control:end"}],"kind":"toolgraph.control-plan","limits":{"maximum_agent_calls":2,"maximum_cycles":0,"maximum_parallelism":2},"nodes":[{"agent_call":false,"id":"control:start","kind":"start","principals":[]},{"agent_call":true,"id":"workflow:draft","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["codex"],"timeout_seconds":300},{"agent_call":true,"id":"workflow:fix","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["kimi"],"timeout_seconds":300},{"agent_call":false,"id":"control:validator:final","kind":"validator","principals":[],"validator_scope":"workspace_result"},{"agent_call":false,"id":"control:end","kind":"end","principals":[]}],"principals":[{"candidate_tools":["workspace::read","workspace::write"],"id":"codex"},{"candidate_tools":["workspace::read","workspace::write"],"id":"kimi"}],"producer":{"name":"syncmill","version":"0.2.0"},"run_id":"wf-6c587c6f51e941d8","schema_version":1} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/executions/wfx-dcb87bb741f14738bbc1d2c5/control-preflight.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/executions/wfx-dcb87bb741f14738bbc1d2c5/control-preflight.json new file mode 100644 index 0000000..4f11494 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/executions/wfx-dcb87bb741f14738bbc1d2c5/control-preflight.json @@ -0,0 +1 @@ +{"schema_version": 1, "kind": "toolgraph.control-preflight", "run_id": "wf-6c587c6f51e941d8", "created_at": "2026-11-10T00:00:00+00:00", "mode": "advisory", "profile": "review", "plan_digest": "sha256:d9f2f0b88948d39b838448957615a461cf8baa5c854ed879fbd8644db6372079", "graph_state": {"instance_id": "frozen-graph", "generation": 7}, "decision": "advisory_allow", "evaluations": [{"principal": "codex", "agent_found": true, "eligible": ["workspace::read", "workspace::write"], "rejected": []}, {"principal": "kimi", "agent_found": true, "eligible": ["workspace::read", "workspace::write"], "rejected": []}], "findings": []} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-1/commit.json new file mode 100644 index 0000000..d59a8be --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-6c587c6f51e941d8", "node_id": "draft", "lease_id": "1d6e1263a5e64142a863168c44be5e39", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "ab4d22248a7da10b83f65a86efc4cd504fbfd32a870c9a1fa2271c61982455a8"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-1/result.json new file mode 100644 index 0000000..b56824f --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-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-6c587c6f51e941d8", + "node_id": "draft", + "lease_id": "1d6e1263a5e64142a863168c44be5e39", + "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/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/1d6e1263a5e64142a863168c44be5e39-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/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/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/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-1/commit.json new file mode 100644 index 0000000..c5633f4 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-6c587c6f51e941d8", "node_id": "fix", "lease_id": "1d6e1263a5e64142a863168c44be5e39", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "aac21f043fb90de2b50bd88997777cfcc18e208047346c641072e5aaeadc7653"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-1/result.json new file mode 100644 index 0000000..def5833 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-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-6c587c6f51e941d8", + "node_id": "fix", + "lease_id": "1d6e1263a5e64142a863168c44be5e39", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/1d6e1263a5e64142a863168c44be5e39-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/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/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.2.0/control-completed/workflows/wf-6c587c6f51e941d8/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/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/control-completed/workflows/wf-6c587c6f51e941d8/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/wf-6c587c6f51e941d8/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/control-completed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/workflows.db new file mode 100644 index 0000000..504fc46 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/control-completed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/.locks/wf-6494f914db7441a1.lock b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/.locks/wf-6494f914db7441a1.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/wf-6494f914db7441a1/control-plan.json b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/wf-6494f914db7441a1/control-plan.json new file mode 100644 index 0000000..0456f87 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/wf-6494f914db7441a1/control-plan.json @@ -0,0 +1 @@ +{"edges":[{"decided_by":"code","from":"control:start","kind":"always","to":"workflow:draft"},{"decided_by":"code","from":"workflow:draft","kind":"success","to":"workflow:fix"},{"decided_by":"code","from":"workflow:draft","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"success","to":"control:validator:final"},{"decided_by":"code","from":"control:validator:final","kind":"success","to":"control:end"},{"decided_by":"code","from":"control:validator:final","kind":"failure","to":"control:end"}],"kind":"toolgraph.control-plan","limits":{"maximum_agent_calls":2,"maximum_cycles":0,"maximum_parallelism":2},"nodes":[{"agent_call":false,"id":"control:start","kind":"start","principals":[]},{"agent_call":true,"id":"workflow:draft","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["codex"],"timeout_seconds":300},{"agent_call":true,"id":"workflow:fix","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["kimi"],"timeout_seconds":300},{"agent_call":false,"id":"control:validator:final","kind":"validator","principals":[],"validator_scope":"workspace_result"},{"agent_call":false,"id":"control:end","kind":"end","principals":[]}],"principals":[{"candidate_tools":["workspace::read","workspace::write"],"id":"codex"},{"candidate_tools":["workspace::read","workspace::write"],"id":"kimi"}],"producer":{"name":"syncmill","version":"0.2.0"},"run_id":"wf-6494f914db7441a1","schema_version":1} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/wf-6494f914db7441a1/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/wf-6494f914db7441a1/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/wf-6494f914db7441a1/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/control-planned/workflows/wf-6494f914db7441a1/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/wf-6494f914db7441a1/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/wf-6494f914db7441a1/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/control-planned/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/workflows.db new file mode 100644 index 0000000..90b02a7 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/control-planned/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/applications/20261109T213824926811Z.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/applications/20261109T213824926811Z.json new file mode 100644 index 0000000..bdc780c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/applications/20261109T213824926811Z.json @@ -0,0 +1,7 @@ +{ + "applied_at": "2026-11-09T21:38:24.926834+00:00", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpqw4tqlsq/repo", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "apply_id": "620e9b6d-1aae-4127-ba41-432286be6cd9", + "run_id": "wf-3a0e23f792ce42d7-final" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/manifest.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/manifest.json new file mode 100644 index 0000000..f51269b --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/manifest.json @@ -0,0 +1,45 @@ +{ + "schema_version": 1, + "run_id": "wf-3a0e23f792ce42d7-final", + "project_id": "frozen-workflows-apply-receipted", + "parent_run_id": null, + "created_at": "2026-11-09T21:38:22.454245+00:00", + "updated_at": "2026-11-09T21:38:24.891702+00:00", + "state": "applying", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpqw4tqlsq/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "d78d7627f3e2f7122b0e19f5f4e4b63083861447", + "task_id": "f38ec304-61b1-46dc-b6c2-4fd9b6d51bc2", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-3a0e23f792ce42d7", + "node_id": "fix", + "final_agent": "kimi-code", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "f936bfa8cdace509a32990a40c207a5da9d7b366034939f62c7741780e604b5b", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved": { + "workflow_id": "wf-3a0e23f792ce42d7", + "run_id": "wf-3a0e23f792ce42d7-final", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved_at": "2026-11-09T21:38:22.464744+00:00" + } + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null, + "apply_id": "620e9b6d-1aae-4127-ba41-432286be6cd9", + "apply_owner": "16574-ac25d7f4", + "apply_started_at": "2026-11-09T21:38:24.891591+00:00" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/result.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-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/apply-receipted/runs/wf-3a0e23f792ce42d7-final/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/runs/wf-3a0e23f792ce42d7-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/apply-receipted/workflows/.locks/wf-3a0e23f792ce42d7.lock b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/.locks/wf-3a0e23f792ce42d7.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-1/commit.json new file mode 100644 index 0000000..e43c330 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-3a0e23f792ce42d7", "node_id": "draft", "lease_id": "40d12bc54e174ed59233a2ab26cb6e52", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "b6496010f12bbb36cf374ef497dc5b391c019f20dc2537eb224a3e8678e12951"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-1/result.json new file mode 100644 index 0000000..09d908f --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-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-3a0e23f792ce42d7", + "node_id": "draft", + "lease_id": "40d12bc54e174ed59233a2ab26cb6e52", + "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/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/40d12bc54e174ed59233a2ab26cb6e52-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/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/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/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-1/commit.json new file mode 100644 index 0000000..f67ea74 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-3a0e23f792ce42d7", "node_id": "fix", "lease_id": "40d12bc54e174ed59233a2ab26cb6e52", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "f936bfa8cdace509a32990a40c207a5da9d7b366034939f62c7741780e604b5b"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-1/result.json new file mode 100644 index 0000000..54c1d76 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-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-3a0e23f792ce42d7", + "node_id": "fix", + "lease_id": "40d12bc54e174ed59233a2ab26cb6e52", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/40d12bc54e174ed59233a2ab26cb6e52-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/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/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/apply-receipted/workflows/wf-3a0e23f792ce42d7/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/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/apply-receipted/workflows/wf-3a0e23f792ce42d7/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/wf-3a0e23f792ce42d7/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/apply-receipted/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/workflows.db new file mode 100644 index 0000000..3e150c1 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/apply-receipted/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-final/manifest.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-final/manifest.json new file mode 100644 index 0000000..ae80897 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-final/manifest.json @@ -0,0 +1,45 @@ +{ + "schema_version": 1, + "run_id": "wf-d17d6f7b1a6c4bb1-final", + "project_id": "frozen-workflows-apply-unreceipted", + "parent_run_id": null, + "created_at": "2026-11-09T21:38:16.936452+00:00", + "updated_at": "2026-11-09T21:38:19.277904+00:00", + "state": "applying", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpqw4tqlsq/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "59d8c96e77512a91a4b9752f00e33315363bfb37", + "task_id": "3e86baeb-9b6b-4a9c-a756-536b9e9a376a", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-d17d6f7b1a6c4bb1", + "node_id": "fix", + "final_agent": "kimi-code", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "7e52e31b9969d3ed6df7f94663717d23e9547f5195baf4688315c8f7670c392c", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved": { + "workflow_id": "wf-d17d6f7b1a6c4bb1", + "run_id": "wf-d17d6f7b1a6c4bb1-final", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved_at": "2026-11-09T21:38:16.946837+00:00" + } + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null, + "apply_id": "67262ddd-b03e-4b0c-80e3-df075500b56e", + "apply_owner": "16453-9203103d", + "apply_started_at": "2026-11-09T21:38:19.277787+00:00" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-final/result.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-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/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-final/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/runs/wf-d17d6f7b1a6c4bb1-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/apply-unreceipted/workflows/.locks/wf-d17d6f7b1a6c4bb1.lock b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/.locks/wf-d17d6f7b1a6c4bb1.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-1/commit.json new file mode 100644 index 0000000..7d0666c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-d17d6f7b1a6c4bb1", "node_id": "draft", "lease_id": "718425862f0f4bf19c80094f5f12983c", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "e8bebb094d1bee80e3b48efb04ab8b8dc90894251d87387d0722cf071be5e845"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-1/result.json new file mode 100644 index 0000000..1c0c849 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-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-d17d6f7b1a6c4bb1", + "node_id": "draft", + "lease_id": "718425862f0f4bf19c80094f5f12983c", + "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/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/718425862f0f4bf19c80094f5f12983c-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/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/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/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-1/commit.json new file mode 100644 index 0000000..55e8753 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-d17d6f7b1a6c4bb1", "node_id": "fix", "lease_id": "718425862f0f4bf19c80094f5f12983c", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "7e52e31b9969d3ed6df7f94663717d23e9547f5195baf4688315c8f7670c392c"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-1/result.json new file mode 100644 index 0000000..ab311cc --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-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-d17d6f7b1a6c4bb1", + "node_id": "fix", + "lease_id": "718425862f0f4bf19c80094f5f12983c", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/718425862f0f4bf19c80094f5f12983c-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/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/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/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/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/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/wf-d17d6f7b1a6c4bb1/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/apply-unreceipted/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/workflows.db new file mode 100644 index 0000000..b3862cc Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/apply-unreceipted/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bindings-2b.json b/tests/fixtures/persisted/workflows/v0.3.0/bindings-2b.json new file mode 100644 index 0000000..40c226f --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bindings-2b.json @@ -0,0 +1,140 @@ +{ + "release": "0.3.0", + "box_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpqw4tqlsq", + "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": { + "bundle-saved": { + "project_id": "frozen-workflows-bundle-saved", + "workflow_ids": [ + "wf-061b5a03465742aa" + ], + "states": { + "wf-061b5a03465742aa": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "16109@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "bundle-saved-recovered": { + "project_id": "frozen-workflows-bundle-saved", + "workflow_ids": [ + "wf-061b5a03465742aa" + ], + "states": { + "wf-061b5a03465742aa": "recovery_required" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "control-planned": { + "project_id": "frozen-workflows-control-planned", + "workflow_ids": [ + "wf-68b63dd2ed314937" + ], + "states": { + "wf-68b63dd2ed314937": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "control-completed": { + "project_id": "frozen-workflows-control-completed", + "workflow_ids": [ + "wf-2c174f13cfcd4c62" + ], + "states": { + "wf-2c174f13cfcd4c62": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-2c174f13cfcd4c62-final" + ], + "run_artifacts": [ + "wf-2c174f13cfcd4c62-final" + ], + "wal": [] + }, + "apply-unreceipted": { + "project_id": "frozen-workflows-apply-unreceipted", + "workflow_ids": [ + "wf-d17d6f7b1a6c4bb1" + ], + "states": { + "wf-d17d6f7b1a6c4bb1": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-d17d6f7b1a6c4bb1-final" + ], + "run_artifacts": [ + "wf-d17d6f7b1a6c4bb1-final" + ], + "wal": [] + }, + "apply-receipted": { + "project_id": "frozen-workflows-apply-receipted", + "workflow_ids": [ + "wf-3a0e23f792ce42d7" + ], + "states": { + "wf-3a0e23f792ce42d7": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-3a0e23f792ce42d7-final" + ], + "run_artifacts": [ + "wf-3a0e23f792ce42d7-final" + ], + "wal": [] + } + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/.locks/wf-061b5a03465742aa.lock b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/.locks/wf-061b5a03465742aa.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/commit.json new file mode 100644 index 0000000..9a11ddb --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-061b5a03465742aa", "node_id": "draft", "lease_id": "77aeb311175645c1b474da6302ce6417", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "b01593d368a29089287b11d64dca38551272110d0e5468135a17e76453298db8"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/result.json new file mode 100644 index 0000000..06b04f8 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-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-061b5a03465742aa", + "node_id": "draft", + "lease_id": "77aeb311175645c1b474da6302ce6417", + "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/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-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/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/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/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/commit.json new file mode 100644 index 0000000..423a703 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-061b5a03465742aa", "node_id": "fix", "lease_id": "77aeb311175645c1b474da6302ce6417", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "b5dca47eb872174210d8069cac35aaedc42c8b608e9262b9e1f19a79fdc72c4e"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/result.json new file mode 100644 index 0000000..030e5f7 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/result.json @@ -0,0 +1,39 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.09, + "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-061b5a03465742aa", + "node_id": "fix", + "lease_id": "77aeb311175645c1b474da6302ce6417", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 7, + "output_tokens": 3, + "cache_read_tokens": null, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-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/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/stream.log new file mode 100644 index 0000000..e2f7891 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/nodes/fix/stream.log @@ -0,0 +1,3 @@ +{"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"} +{"role": "meta", "type": "usage", "usage": {"input_tokens": 7, "output_tokens": 3}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/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/bundle-saved-recovered/workflows/wf-061b5a03465742aa/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/wf-061b5a03465742aa/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/bundle-saved-recovered/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/workflows.db new file mode 100644 index 0000000..cc04924 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved-recovered/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/.locks/wf-061b5a03465742aa.lock b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/.locks/wf-061b5a03465742aa.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/commit.json new file mode 100644 index 0000000..9a11ddb --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-061b5a03465742aa", "node_id": "draft", "lease_id": "77aeb311175645c1b474da6302ce6417", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "b01593d368a29089287b11d64dca38551272110d0e5468135a17e76453298db8"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/result.json new file mode 100644 index 0000000..06b04f8 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-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-061b5a03465742aa", + "node_id": "draft", + "lease_id": "77aeb311175645c1b474da6302ce6417", + "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/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/77aeb311175645c1b474da6302ce6417-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/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/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/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/commit.json new file mode 100644 index 0000000..423a703 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-061b5a03465742aa", "node_id": "fix", "lease_id": "77aeb311175645c1b474da6302ce6417", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "b5dca47eb872174210d8069cac35aaedc42c8b608e9262b9e1f19a79fdc72c4e"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/result.json new file mode 100644 index 0000000..030e5f7 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/result.json @@ -0,0 +1,39 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.09, + "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-061b5a03465742aa", + "node_id": "fix", + "lease_id": "77aeb311175645c1b474da6302ce6417", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 7, + "output_tokens": 3, + "cache_read_tokens": null, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/77aeb311175645c1b474da6302ce6417-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/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/stream.log new file mode 100644 index 0000000..e2f7891 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/nodes/fix/stream.log @@ -0,0 +1,3 @@ +{"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"} +{"role": "meta", "type": "usage", "usage": {"input_tokens": 7, "output_tokens": 3}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/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/bundle-saved/workflows/wf-061b5a03465742aa/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/wf-061b5a03465742aa/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/bundle-saved/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/workflows.db new file mode 100644 index 0000000..26b3dd9 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/bundle-saved/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/wf-2c174f13cfcd4c62-final/manifest.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/wf-2c174f13cfcd4c62-final/manifest.json new file mode 100644 index 0000000..5351756 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/wf-2c174f13cfcd4c62-final/manifest.json @@ -0,0 +1,43 @@ +{ + "schema_version": 1, + "run_id": "wf-2c174f13cfcd4c62-final", + "project_id": "frozen-workflows-control-completed", + "parent_run_id": null, + "created_at": "2026-11-09T21:38:11.293312+00:00", + "updated_at": "2026-11-09T21:38:11.303237+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpqw4tqlsq/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "00bff210e7f98997a09e95f2ddf4e2b724b873ba", + "task_id": "5e1ac64c-3a5d-489d-b53d-b6c9dddede8f", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-2c174f13cfcd4c62", + "node_id": "fix", + "final_agent": "kimi-code", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "ed1d8b8d38a04baed3ef4fe2633925f5903ac39fed181f734753158ddb57f714", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "control_plan_digest": "sha256:590e24d932bd99059d1474971ee7fd9d6e820c17d1d58c06b2a4ca253a25c5f3", + "approved": { + "workflow_id": "wf-2c174f13cfcd4c62", + "run_id": "wf-2c174f13cfcd4c62-final", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved_at": "2026-11-09T21:38:11.303231+00:00" + } + }, + "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/control-completed/runs/wf-2c174f13cfcd4c62-final/result.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/wf-2c174f13cfcd4c62-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/wf-2c174f13cfcd4c62-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/control-completed/runs/wf-2c174f13cfcd4c62-final/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/wf-2c174f13cfcd4c62-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/runs/wf-2c174f13cfcd4c62-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/control-completed/workflows/.locks/wf-2c174f13cfcd4c62.lock b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/.locks/wf-2c174f13cfcd4c62.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/control-plan.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/control-plan.json new file mode 100644 index 0000000..dd8f8b5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/control-plan.json @@ -0,0 +1 @@ +{"edges":[{"decided_by":"code","from":"control:start","kind":"always","to":"workflow:draft"},{"decided_by":"code","from":"workflow:draft","kind":"success","to":"workflow:fix"},{"decided_by":"code","from":"workflow:draft","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"success","to":"control:validator:final"},{"decided_by":"code","from":"control:validator:final","kind":"success","to":"control:end"},{"decided_by":"code","from":"control:validator:final","kind":"failure","to":"control:end"}],"kind":"toolgraph.control-plan","limits":{"maximum_agent_calls":2,"maximum_cycles":0,"maximum_parallelism":2},"nodes":[{"agent_call":false,"id":"control:start","kind":"start","principals":[]},{"agent_call":true,"id":"workflow:draft","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["codex"],"timeout_seconds":300},{"agent_call":true,"id":"workflow:fix","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["kimi"],"timeout_seconds":300},{"agent_call":false,"id":"control:validator:final","kind":"validator","principals":[],"validator_scope":"workspace_result"},{"agent_call":false,"id":"control:end","kind":"end","principals":[]}],"principals":[{"candidate_tools":["workspace::read","workspace::write"],"id":"codex"},{"candidate_tools":["workspace::read","workspace::write"],"id":"kimi"}],"producer":{"name":"syncmill","version":"0.3.0"},"run_id":"wf-2c174f13cfcd4c62","schema_version":1} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/executions/wfx-7b1756745c774ac5a4238b32/control-plan.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/executions/wfx-7b1756745c774ac5a4238b32/control-plan.json new file mode 100644 index 0000000..dd8f8b5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/executions/wfx-7b1756745c774ac5a4238b32/control-plan.json @@ -0,0 +1 @@ +{"edges":[{"decided_by":"code","from":"control:start","kind":"always","to":"workflow:draft"},{"decided_by":"code","from":"workflow:draft","kind":"success","to":"workflow:fix"},{"decided_by":"code","from":"workflow:draft","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"success","to":"control:validator:final"},{"decided_by":"code","from":"control:validator:final","kind":"success","to":"control:end"},{"decided_by":"code","from":"control:validator:final","kind":"failure","to":"control:end"}],"kind":"toolgraph.control-plan","limits":{"maximum_agent_calls":2,"maximum_cycles":0,"maximum_parallelism":2},"nodes":[{"agent_call":false,"id":"control:start","kind":"start","principals":[]},{"agent_call":true,"id":"workflow:draft","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["codex"],"timeout_seconds":300},{"agent_call":true,"id":"workflow:fix","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["kimi"],"timeout_seconds":300},{"agent_call":false,"id":"control:validator:final","kind":"validator","principals":[],"validator_scope":"workspace_result"},{"agent_call":false,"id":"control:end","kind":"end","principals":[]}],"principals":[{"candidate_tools":["workspace::read","workspace::write"],"id":"codex"},{"candidate_tools":["workspace::read","workspace::write"],"id":"kimi"}],"producer":{"name":"syncmill","version":"0.3.0"},"run_id":"wf-2c174f13cfcd4c62","schema_version":1} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/executions/wfx-7b1756745c774ac5a4238b32/control-preflight.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/executions/wfx-7b1756745c774ac5a4238b32/control-preflight.json new file mode 100644 index 0000000..4b55abb --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/executions/wfx-7b1756745c774ac5a4238b32/control-preflight.json @@ -0,0 +1 @@ +{"schema_version": 1, "kind": "toolgraph.control-preflight", "run_id": "wf-2c174f13cfcd4c62", "created_at": "2026-11-10T00:00:00+00:00", "mode": "advisory", "profile": "review", "plan_digest": "sha256:590e24d932bd99059d1474971ee7fd9d6e820c17d1d58c06b2a4ca253a25c5f3", "graph_state": {"instance_id": "frozen-graph", "generation": 7}, "decision": "advisory_allow", "evaluations": [{"principal": "codex", "agent_found": true, "eligible": ["workspace::read", "workspace::write"], "rejected": []}, {"principal": "kimi", "agent_found": true, "eligible": ["workspace::read", "workspace::write"], "rejected": []}], "findings": []} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-1/commit.json new file mode 100644 index 0000000..4392d6a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-2c174f13cfcd4c62", "node_id": "draft", "lease_id": "8c977435d4644e4ba789d45f65f14991", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "fe0b32d7bfef14441d2efe482e4529024c28c4e001fe86b76ee6943bf7a66dbb"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-1/result.json new file mode 100644 index 0000000..11e6a06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-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-2c174f13cfcd4c62", + "node_id": "draft", + "lease_id": "8c977435d4644e4ba789d45f65f14991", + "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/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/8c977435d4644e4ba789d45f65f14991-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/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/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/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-1/commit.json new file mode 100644 index 0000000..4af641c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-2c174f13cfcd4c62", "node_id": "fix", "lease_id": "8c977435d4644e4ba789d45f65f14991", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "ed1d8b8d38a04baed3ef4fe2633925f5903ac39fed181f734753158ddb57f714"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-1/result.json new file mode 100644 index 0000000..72c62f6 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-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-2c174f13cfcd4c62", + "node_id": "fix", + "lease_id": "8c977435d4644e4ba789d45f65f14991", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/8c977435d4644e4ba789d45f65f14991-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/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/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/control-completed/workflows/wf-2c174f13cfcd4c62/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/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/control-completed/workflows/wf-2c174f13cfcd4c62/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/wf-2c174f13cfcd4c62/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/control-completed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/workflows.db new file mode 100644 index 0000000..2256c27 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/control-completed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/.locks/wf-68b63dd2ed314937.lock b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/.locks/wf-68b63dd2ed314937.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/wf-68b63dd2ed314937/control-plan.json b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/wf-68b63dd2ed314937/control-plan.json new file mode 100644 index 0000000..bd9158d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/wf-68b63dd2ed314937/control-plan.json @@ -0,0 +1 @@ +{"edges":[{"decided_by":"code","from":"control:start","kind":"always","to":"workflow:draft"},{"decided_by":"code","from":"workflow:draft","kind":"success","to":"workflow:fix"},{"decided_by":"code","from":"workflow:draft","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"failure","to":"control:end"},{"decided_by":"code","from":"workflow:fix","kind":"success","to":"control:validator:final"},{"decided_by":"code","from":"control:validator:final","kind":"success","to":"control:end"},{"decided_by":"code","from":"control:validator:final","kind":"failure","to":"control:end"}],"kind":"toolgraph.control-plan","limits":{"maximum_agent_calls":2,"maximum_cycles":0,"maximum_parallelism":2},"nodes":[{"agent_call":false,"id":"control:start","kind":"start","principals":[]},{"agent_call":true,"id":"workflow:draft","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["codex"],"timeout_seconds":300},{"agent_call":true,"id":"workflow:fix","kind":"agent","max_invocations":1,"max_parallelism":1,"principals":["kimi"],"timeout_seconds":300},{"agent_call":false,"id":"control:validator:final","kind":"validator","principals":[],"validator_scope":"workspace_result"},{"agent_call":false,"id":"control:end","kind":"end","principals":[]}],"principals":[{"candidate_tools":["workspace::read","workspace::write"],"id":"codex"},{"candidate_tools":["workspace::read","workspace::write"],"id":"kimi"}],"producer":{"name":"syncmill","version":"0.3.0"},"run_id":"wf-68b63dd2ed314937","schema_version":1} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/wf-68b63dd2ed314937/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/wf-68b63dd2ed314937/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/wf-68b63dd2ed314937/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/control-planned/workflows/wf-68b63dd2ed314937/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/wf-68b63dd2ed314937/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/wf-68b63dd2ed314937/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/control-planned/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/workflows.db new file mode 100644 index 0000000..92f226b Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/control-planned/workflows/workflows.db differ diff --git a/tests/test_frozen_workflows.py b/tests/test_frozen_workflows.py index 385983c..6da44cc 100644 --- a/tests/test_frozen_workflows.py +++ b/tests/test_frozen_workflows.py @@ -53,6 +53,18 @@ } #: v0.3.0 added plans from a document and their revisions. SCENARIOS_V030 = {"created": ["planned"], "revised": ["planned", "planned"]} +#: Written later by ``freeze.py --part 2b`` (``bindings-2b.json``), for both +#: releases: a bundle saved just before the crash, control-plan workflows, an +#: interrupted apply of a final artifact. +SCENARIOS_2B = { + "bundle-saved": ["running"], + "bundle-saved-recovered": ["recovery_required"], + "control-planned": ["planned"], + "control-completed": ["completed"], + "apply-unreceipted": ["completed"], + "apply-receipted": ["completed"], +} +ALL_SCENARIOS = [*SCENARIOS, *SCENARIOS_V030, *SCENARIOS_2B] #: Within C ``int`` (``os.kill`` accepts it) and above any real pid_max, so #: the liveness probe answers "no such process". @@ -61,11 +73,24 @@ def scenarios(release: str) -> dict[str, list[str]]: - return {**SCENARIOS, **(SCENARIOS_V030 if release != "v0.2.0" else {})} + return {**SCENARIOS, **(SCENARIOS_V030 if release != "v0.2.0" else {}), **SCENARIOS_2B} + +def _binding_files(release: str) -> dict[str, dict]: + """Every bindings document of a release: one per generator part.""" + return { + path.name: json.loads(path.read_text()) + for path in sorted((FROZEN / release).glob("bindings*.json")) + } -def _bindings(release: str) -> dict: - return json.loads((FROZEN / release / "bindings.json").read_text()) + +def _bindings(release: str, scenario: str) -> dict: + """The bindings document that holds ``scenario``: its machine values + (box root, host) are that generator run's own.""" + [document] = [ + doc for doc in _binding_files(release).values() if scenario in doc["scenarios"] + ] + return document def _cells(conn: sqlite3.Connection) -> dict[tuple[str, str], list]: @@ -95,7 +120,7 @@ class Frozen: def __init__(self, release: str, scenario: str, tmp_path: Path, monkeypatch): self.release = release self.scenario = scenario - self.bindings = _bindings(release) + self.bindings = _bindings(release, scenario) self.facts = self.bindings["scenarios"][scenario] self.dir = FROZEN / release / scenario self.root = tmp_path.resolve() @@ -164,6 +189,9 @@ def _install(self) -> None: # 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) + # An apply receipt names the repository once and no fingerprint. + for receipt in self.project_dir.glob("runs/*/applications/*.json"): + self._replace_bytes(receipt, fingerprints=0, paths=1) def _replace_bytes(self, path: Path, *, fingerprints: int, paths: int) -> None: data = path.read_bytes() @@ -207,10 +235,17 @@ 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(): + files = _binding_files(release) + assert sorted(files) == ["bindings-2b.json", "bindings.json"] + named = [name for doc in files.values() for name in doc["scenarios"]] + assert len(named) == len(set(named)), release + assert sorted(named) == sorted(scenarios(release)) + assert sorted(files["bindings-2b.json"]["scenarios"]) == sorted(SCENARIOS_2B) + for bindings in files.values(): + assert bindings["release"] == release.removeprefix("v") + for name, facts in ( + item for doc in files.values() for item in doc["scenarios"].items() + ): assert list(facts["states"].values()) == scenarios(release)[name], name for stored in facts["source_fingerprints"]: assert fingerprint_version(stored) == 1 @@ -235,7 +270,7 @@ def _each(release: str) -> list[str]: return list(scenarios(release)) -@pytest.mark.parametrize("scenario", list(SCENARIOS) + list(SCENARIOS_V030)) +@pytest.mark.parametrize("scenario", ALL_SCENARIOS) 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}") @@ -290,7 +325,7 @@ def _fresh_heartbeat(frozen: "Frozen"): #: Scenarios whose released lease owner is dead; HEAD's show/status recover it. -DEAD_OWNER = {"running", "unapproved-running"} +DEAD_OWNER = {"running", "unapproved-running", "bundle-saved"} #: The final state each node of the frozen spec was left in, per scenario. NODES = { "planned": ("pending", "pending"), @@ -305,13 +340,19 @@ def _fresh_heartbeat(frozen: "Frozen"): "running": ("completed", "running"), "unapproved-running": ("completed", "completed"), "recovery-required": ("completed", "interrupted"), + "bundle-saved": ("completed", "running"), + "bundle-saved-recovered": ("completed", "interrupted"), + "control-planned": ("pending", "pending"), + "control-completed": ("completed", "completed"), + "apply-unreceipted": ("completed", "completed"), + "apply-receipted": ("completed", "completed"), } # ── reading every released workflow ─────────────────────────────────────── -@pytest.mark.parametrize("scenario", list(SCENARIOS) + list(SCENARIOS_V030)) +@pytest.mark.parametrize("scenario", ALL_SCENARIOS) 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}") @@ -825,6 +866,7 @@ def __init__(self, agent_id: str, output: str, *, fix: bool = False): self.output = output self.fix = fix self.calls: list[str] = [] + self.timeouts: list[int] = [] def execute(self, prompt, workdir, timeout): raise AssertionError("a released plan needs no planner call") @@ -835,6 +877,7 @@ async def aexecute(self, prompt, workdir, timeout): from syncmill.models import TaskResult, TaskStatus self.calls.append(prompt) + self.timeouts.append(timeout) if self.fix: (Path(workdir) / "calc.py").write_text(FIXED, encoding="utf-8") return TaskResult( @@ -843,20 +886,23 @@ async def aexecute(self, prompt, workdir, timeout): ) -def _workflow_env(frozen: Frozen, monkeypatch) -> None: - """The configuration every released scenario was written under.""" +def _workflow_env(frozen: Frozen, monkeypatch, *, gate: bool | None = None) -> None: + """The configuration every released scenario was written under (``gate`` + overrides whether the gate is configured).""" monkeypatch.setenv("SYNCMILL_AGENT_PRIORITY", json.dumps(["codex", "kimi-code"])) monkeypatch.setenv("SYNCMILL_CLAUDE__ENABLED", "false") - if frozen.scenario != "no-changes": + if gate if gate is not None else frozen.scenario != "no-changes": monkeypatch.setenv("SYNCMILL_GATE__COMMANDS", json.dumps([GATE_COMMAND])) + else: + monkeypatch.setenv("SYNCMILL_GATE__COMMANDS", "[]") -def _coordinator(frozen: Frozen, monkeypatch): +def _coordinator(frozen: Frozen, monkeypatch, *, gate: bool | None = None): from conftest import FakeRegistry from syncmill.project import load_project_config from syncmill.workflow import WorkflowCoordinator - _workflow_env(frozen, monkeypatch) + _workflow_env(frozen, monkeypatch, gate=gate) _, config = load_project_config(frozen.repo) agents = { "codex": NodeAgent("codex", "HEAD draft."), @@ -1445,3 +1491,374 @@ def no_toolgraph(*args, **kwargs): assert frozen.row() == before assert agents["codex"].calls == agents["kimi-code"].calls == [] assert toolgraph_calls == [] + + +# ── part 2b: a bundle saved just before the crash ───────────────────────── + + +@pytest.mark.parametrize("scenario", ["bundle-saved", "bundle-saved-recovered"]) +def test_resume_reclaims_a_released_bundle_saved_before_the_crash( + release, scenario, tmp_path, monkeypatch, capsys +): + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + coordinator, agents = _coordinator(frozen, monkeypatch) + released = _before_deadline(frozen, monkeypatch) + fix = released["nodes"]["fix"] + # The crash came after the bundle was saved and before the manifest + # recorded the node's outcome. + assert fix["state"] == ("running" if scenario == "bundle-saved" else "interrupted") + assert (fix["bundle_sha256"], fix["usage_recorded"], fix["attempts"]) == (None, False, 1) + bundle = frozen.project_dir / "workflows" / frozen.workflow_id / Path(fix["output_path"]).parent + stored = json.loads((bundle / "result.json").read_bytes()) + assert (stored["status"], stored["gate"]["passed"]) == ("completed", True) + assert (stored["usage"]["total_input_tokens"], stored["usage"]["output_tokens"]) == (7, 3) + patch = (bundle / "winner.patch").read_bytes() + saved = _capture_final_results(monkeypatch) + + done = coordinator.run(frozen.workflow_id, resume=True) + + assert done.state == "completed" + # The final result is rebuilt from the released bundle: its output and + # its usage (TokenUsage.from_mapping), as the release saved them. + [result] = saved + assert result.output == stored["output"] + assert result.usage is not None + assert (result.usage.total_input_tokens, result.usage.output_tokens) == (7, 3) + # Reclaimed, not re-run: no provider call, and the call the release + # reserved before dispatch stays counted once. + assert agents["codex"].calls == agents["kimi-code"].calls == [] + assert done.manifest["calls_used"] == released["calls_used"] == 3 + node = done.manifest["nodes"]["fix"] + assert (node["state"], node["attempts"], node["output_path"]) == ( + "completed", 1, fix["output_path"] + ) + # The bundle's usage joins the totals exactly once. + assert (done.manifest["usage"]["input_tokens"], done.manifest["usage"]["output_tokens"]) == ( + released["usage"]["input_tokens"] + 7, released["usage"]["output_tokens"] + 3 + ) + _approved_final(coordinator, done, "kimi-code") + assert coordinator.artifacts.patch(done.final_run_id) == patch + error, lines = _render(done, frozen, capsys, released) + assert error == "" and f"[syncmill] final run: {done.final_run_id}" in lines + + +def _capture_final_results(monkeypatch) -> list: + """Every TaskResult handed to the artifact store for saving.""" + from syncmill.artifacts import ArtifactStore + + saved: list = [] + original = ArtifactStore.save + + def save(self, **kwargs): + saved.append(kwargs["result"]) + return original(self, **kwargs) + + monkeypatch.setattr(ArtifactStore, "save", save) + return saved + + +def _damage_bundle_patch(bundle: Path) -> None: + (bundle / "winner.patch").write_bytes(b"not the saved patch\n") + + +def _damage_bundle_patch_recommitted(bundle: Path) -> None: + """A replaced patch whose commit record was recomputed to match: only + the patch digest in ``result.json`` still tells the difference.""" + from syncmill.workflow import bundle_digest + + patch = b"not the saved patch\n" + (bundle / "winner.patch").write_bytes(patch) + commit = json.loads((bundle / "commit.json").read_text()) + commit["bundle_sha256"] = bundle_digest((bundle / "result.json").read_bytes(), patch) + (bundle / "commit.json").write_text(json.dumps(commit), encoding="utf-8") + + +def _damage_bundle_commit(bundle: Path) -> None: + commit = json.loads((bundle / "commit.json").read_text()) + commit["bundle_sha256"] = "0" * 64 + (bundle / "commit.json").write_text(json.dumps(commit), encoding="utf-8") + + +@pytest.mark.parametrize( + "damage", + [_damage_bundle_patch, _damage_bundle_patch_recommitted, _damage_bundle_commit], + ids=["patch", "patch-recommitted", "commit"], +) +def test_resume_reruns_a_released_bundle_that_does_not_verify( + release, damage, tmp_path, monkeypatch +): + frozen = Frozen(release, "bundle-saved", tmp_path, monkeypatch) + coordinator, agents = _coordinator(frozen, monkeypatch) + released = _before_deadline(frozen, monkeypatch) + fix = released["nodes"]["fix"] + damage(frozen.project_dir / "workflows" / frozen.workflow_id / Path(fix["output_path"]).parent) + + done = coordinator.run(frozen.workflow_id, resume=True) + + assert done.state == "completed" + # Not reclaimed: the node runs again. Its attempt is refunded, the call + # it already spent is not. + assert agents["codex"].calls == [] + assert len(agents["kimi-code"].calls) == 1 + assert done.manifest["nodes"]["fix"]["attempts"] == 1 + assert done.manifest["calls_used"] == released["calls_used"] + 1 + # The damaged bundle's usage is never counted. + assert done.manifest["usage"]["input_tokens"] == released["usage"]["input_tokens"] + _approved_final(coordinator, done, "kimi-code") + assert "+ return a + b" in coordinator.artifacts.patch(done.final_run_id).decode() + + +# ── part 2b: control-plan workflows ─────────────────────────────────────── + +#: The control-plan configuration the 2b scenarios were written under. +TOOLGRAPH_TOOLS = ["workspace::read", "workspace::write"] +TOOLGRAPH_AGENTS = {"codex": "codex", "kimi-code": "kimi"} + + +def _toolgraph(monkeypatch, *, enabled=True, control=True, tools=None, agents=None) -> list: + """Configure Toolgraph and fake it through the preflight seam, answering + each protocol from the request it gets. Returns the recorded argv lists + (with the control plan's bytes for a control preflight).""" + from test_workflow import _fake_control_run + + if enabled: + monkeypatch.setenv("SYNCMILL_INTEGRATION__TOOLGRAPH__ENABLED", "true") + monkeypatch.setenv( + "SYNCMILL_INTEGRATION__TOOLGRAPH__CONTROL_PLAN_ENABLED", "true" if control else "false" + ) + monkeypatch.setenv( + "SYNCMILL_INTEGRATION__TOOLGRAPH__CANDIDATE_TOOLS", json.dumps(tools or TOOLGRAPH_TOOLS) + ) + monkeypatch.setenv( + "SYNCMILL_INTEGRATION__TOOLGRAPH__AGENT_MAP", json.dumps(agents or TOOLGRAPH_AGENTS) + ) + seen: list = [] + answer = _fake_control_run() + + def seam(argv, *args, **kwargs): + if argv[:2] == ["toolgraph", "control-preflight"]: + seen.append(("control", Path(argv[2]).read_bytes())) + elif argv[:1] == ["toolgraph"]: + seen.append(("legacy", argv[2])) + return answer(argv, *args, **kwargs) + + monkeypatch.setattr("syncmill.preflight._sync_run", seam) + return seen + + +def _wider_than_released(monkeypatch) -> None: + """Current limits wider than every limit the 2b scenarios were written + under (deadline 3600 s, parallelism 4, output 1 MiB, agent timeout 300 s).""" + monkeypatch.setenv("SYNCMILL_WORKFLOW__DEADLINE_SECONDS", "7200") + monkeypatch.setenv("SYNCMILL_WORKFLOW__MAX_PARALLEL", "8") + monkeypatch.setenv("SYNCMILL_WORKFLOW__MAX_OUTPUT_BYTES", str(4 * 1024 * 1024)) + monkeypatch.setenv("SYNCMILL_KIMI_CODE__TIMEOUT_SECONDS", "900") + monkeypatch.setenv("SYNCMILL_CODEX__TIMEOUT_SECONDS", "900") + + +def _sha256(data: bytes) -> str: + import hashlib + + return "sha256:" + hashlib.sha256(data).hexdigest() + + +@pytest.mark.parametrize("tight", [False, True], ids=["released-limits", "tighter-config"]) +def test_run_a_released_control_planned_workflow(release, tight, tmp_path, monkeypatch): + seen = _toolgraph(monkeypatch) + if tight: + monkeypatch.setenv("SYNCMILL_WORKFLOW__DEADLINE_SECONDS", "1800") + monkeypatch.setenv("SYNCMILL_WORKFLOW__MAX_PARALLEL", "1") + monkeypatch.setenv("SYNCMILL_WORKFLOW__MAX_OUTPUT_BYTES", "65536") + monkeypatch.setenv("SYNCMILL_KIMI_CODE__TIMEOUT_SECONDS", "120") + else: + # Wider than every released limit: each released one holds. + _wider_than_released(monkeypatch) + frozen = Frozen(release, "control-planned", tmp_path, monkeypatch) + coordinator, agents = _coordinator(frozen, monkeypatch) + released = json.loads(frozen.row()["manifest_json"]) + plan_bytes = frozen.frozen_bytes(f"workflows/{frozen.workflow_id}/control-plan.json") + plan = json.loads(plan_bytes) + stored_timeouts = {row["id"]: row["timeout_seconds"] for row in plan["nodes"] + if row["kind"] == "agent"} + assert released["control_plan"]["digest"] == _sha256(plan_bytes) + assert released["execution_limits"]["max_parallel"] == 4 + assert plan["limits"]["maximum_parallelism"] == 2 + + done = coordinator.run(frozen.workflow_id) + + assert done.state == "completed" + # One control preflight over the released plan's own bytes; no legacy probe. + assert seen == [("control", plan_bytes)] + execution = done.manifest["executions"][-1] + assert execution["control_plan_digest"] == released["control_plan"]["digest"] + evidence = execution["preflight"] + assert (evidence["status"], evidence["control_plan"]["digest"]) == ( + "ok", released["control_plan"]["digest"] + ) + evidence_file = ( + coordinator.store.workflow_dir(frozen.workflow_id) / "executions" + / execution["execution_id"] / "control-preflight.json" + ) + assert evidence["digest"] == _sha256(evidence_file.read_bytes()) + # Each limit is the tighter of the current config and the released one; + # parallelism is further capped by the released plan. + stored = released["execution_limits"] + assert execution["limits"] == { + "deadline_seconds": 1800 if tight else stored["deadline_seconds"], + "max_parallel": 1 if tight else plan["limits"]["maximum_parallelism"], + "max_output_bytes": 65536 if tight else stored["max_output_bytes"], + } + assert agents["codex"].timeouts == [stored_timeouts["workflow:draft"]] + assert agents["kimi-code"].timeouts == [120 if tight else stored_timeouts["workflow:fix"]] + _approved_final(coordinator, done, "kimi-code") + # The released plan file is read, never rewritten. + assert (coordinator.store.workflow_dir(frozen.workflow_id) / "control-plan.json" + ).read_bytes() == plan_bytes + + +@pytest.mark.parametrize( + ("change", "error"), + [ + ({"tools": ["workspace::read"]}, + "Toolgraph agent mapping or candidate tools changed; create a new workflow plan"), + ({"agents": {"codex": "codex", "kimi-code": "kimi-2"}}, + "Toolgraph agent mapping or candidate tools changed; create a new workflow plan"), + ({"gate": False}, "workflow validation topology changed; create a new workflow plan"), + ], + ids=["tools", "agent-map", "gate"], +) +def test_a_released_control_plan_refuses_a_changed_configuration( + release, change, error, tmp_path, monkeypatch +): + from syncmill.workflow import WorkflowError + + change = dict(change) # the parameter is shared by both releases + gate = change.pop("gate", None) + seen = _toolgraph(monkeypatch, **change) + frozen = Frozen(release, "control-planned", tmp_path, monkeypatch) + coordinator, agents = _coordinator(frozen, monkeypatch, gate=gate) + before = frozen.row() + with pytest.raises(WorkflowError) as refused: + coordinator.run(frozen.workflow_id) + assert str(refused.value) == error + assert seen == [] + assert agents["codex"].calls == agents["kimi-code"].calls == [] + assert frozen.row() == before + + +@pytest.mark.parametrize("toolgraph", ["control-plans-off", "toolgraph-off"]) +def test_a_released_control_plan_still_bounds_a_run_without_control_plans( + release, toolgraph, tmp_path, monkeypatch +): + seen = _toolgraph(monkeypatch, enabled=toolgraph == "control-plans-off", control=False) + _wider_than_released(monkeypatch) + frozen = Frozen(release, "control-planned", tmp_path, monkeypatch) + coordinator, agents = _coordinator(frozen, monkeypatch) + released = json.loads(frozen.row()["manifest_json"]) + plan = json.loads(frozen.frozen_bytes(f"workflows/{frozen.workflow_id}/control-plan.json")) + + done = coordinator.run(frozen.workflow_id) + + assert done.state == "completed" + # Without control plans the run makes the lead agent's legacy advisory + # probe; without Toolgraph, none at all. + assert seen == ([("legacy", "codex")] if toolgraph == "control-plans-off" else []) + # The released plan is still read and still bounds the run. + execution = done.manifest["executions"][-1] + assert execution["control_plan_digest"] == released["control_plan"]["digest"] + stored = released["execution_limits"] + assert execution["limits"] == { + "deadline_seconds": stored["deadline_seconds"], + "max_parallel": plan["limits"]["maximum_parallelism"], + "max_output_bytes": stored["max_output_bytes"], + } + timeouts = {row["id"]: row["timeout_seconds"] for row in plan["nodes"] if row["kind"] == "agent"} + assert agents["codex"].timeouts == [timeouts["workflow:draft"]] + assert agents["kimi-code"].timeouts == [timeouts["workflow:fix"]] + + +def test_a_released_control_completed_workflow_reads_as_written(release, tmp_path, monkeypatch): + frozen = Frozen(release, "control-completed", tmp_path, monkeypatch) + released = json.loads(frozen.row()["manifest_json"]) + [execution] = released["executions"] + workflow_dir = f"workflows/{frozen.workflow_id}" + plan_bytes = frozen.frozen_bytes(f"{workflow_dir}/control-plan.json") + evidence_name = f"{workflow_dir}/executions/{execution['execution_id']}/control-preflight.json" + evidence_bytes = frozen.frozen_bytes(evidence_name) + assert execution["preflight"]["digest"] == _sha256(evidence_bytes) + + shown = frozen.cli("workflow", "show", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + assert _lines(shown.output, "control_plan: ") == [ + f"control_plan: {released['control_plan']['digest']}" + ] + assert _lines(shown.output, "execution: ") == [ + f"execution: {execution['execution_id']} action=run status=completed" + ] + assert _lines(shown.output, "preflight: ") == [ + f"preflight: ok generation={execution['preflight']['graph_state']['generation']}" + ] + status = frozen.cli("workflow", "status", frozen.workflow_id) + assert status.exit_code == 0, status.output + assert _store(frozen).read_control_plan(frozen.workflow_id) == plan_bytes + + run_id, run_dir = _final(frozen) + manifest = json.loads((run_dir / "manifest.json").read_text()) + assert manifest["plan"]["control_plan_digest"] == released["control_plan"]["digest"] + conversation = frozen.cli("conversations", "show", run_id) + assert conversation.exit_code == 0, conversation.output + shown = json.loads(conversation.stdout) + assert shown["manifest"] == manifest + assert shown["result"] == json.loads(frozen.frozen_bytes(f"runs/{run_id}/result.json")) + assert shown["result"]["output"] + applied = frozen.cli("apply", run_id) + assert applied.exit_code == 0, applied.output + assert (frozen.repo / "calc.py").read_text() == FIXED + # No reader revalidates or rewrites the released evidence. + assert (frozen.project_dir / evidence_name).read_bytes() == evidence_bytes + assert (frozen.project_dir / workflow_dir / "control-plan.json").read_bytes() == plan_bytes + + +# ── part 2b: an interrupted apply of a released final artifact ──────────── + + +@pytest.mark.parametrize( + ("scenario", "patch_in_source", "outcome", "state"), + [ + ("apply-receipted", True, "completed", "applied"), + ("apply-unreceipted", False, "not applied", "pending"), + ("apply-receipted", False, "manual recovery required", "recovery_required"), + ], + ids=["receipted-applied", "unreceipted", "receipted-undecidable"], +) +def test_recover_a_released_interrupted_workflow_apply( + release, scenario, patch_in_source, outcome, state, tmp_path, monkeypatch +): + from syncmill.artifacts import ArtifactStore + + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + before = json.loads((run_dir / "manifest.json").read_text()) + assert before["state"] == "applying" and "approved" in before["plan"] + receipts = sorted((run_dir / "applications").glob("*.json")) + assert len(receipts) == (1 if scenario == "apply-receipted" else 0) + store = ArtifactStore(frozen.runs_root, frozen.facts["project_id"]) + # The receipt is bound to this apply by id, run and patch digest. + assert store._bound_receipt(run_dir, before) == (receipts[0] if receipts else None) + if patch_in_source: + subprocess.run(["git", "apply", "-"], cwd=frozen.repo, + input=(run_dir / "winner.patch").read_bytes(), check=True) + row = frozen.row() + + recovered = frozen.cli("recover", run_id) + + assert recovered.exit_code == 0, recovered.output + [line] = recovered.output.splitlines() + assert line.startswith(f"Run {run_id}: {outcome} \N{EM DASH} ") + after = json.loads((run_dir / "manifest.json").read_text()) + assert after["state"] == state + # Recovery neither needs nor touches the workflow's approval or its row. + assert after["plan"] == before["plan"] + assert frozen.row() == row + expected_source = FIXED if patch_in_source else frozen.bindings["tree"]["calc.py"] + assert (frozen.repo / "calc.py").read_text() == expected_source