diff --git a/docs/architecture/persisted-contracts.md b/docs/architecture/persisted-contracts.md index 9ff5938..25b4415 100644 --- a/docs/architecture/persisted-contracts.md +++ b/docs/architecture/persisted-contracts.md @@ -53,7 +53,7 @@ A golden over the current writer does not protect these. A reader can stop accep |---|---|---|---| | Artifacts | run `manifest.json`, `result.json`, `winner.patch`, apply receipts (`applications/*.json`), in every state a standalone `run` leaves them (room-, session- and workflow-origin artifacts are frozen with those families) | `runs list` / `show` / `prune`, `conversations list` / `show`, `diff`, `apply`, `recover` (`_bound_receipt`), `continue`, the TUI artifact view | `test_frozen_artifacts.py` (v0.1.2, v0.3.0) | | Rooms | the room database (`rooms.sqlite3`, with the WAL a killed process leaves): rooms, rounds, candidates, members, workspaces, events, `sessions_json`, `selection_json` v1, `provenance_json` v1/v2, `conversation_json` v1; candidate `manifest.json`, patch and output; room-bound run artifacts. No release wrote `conversation_json` v2 or judgment rows | `room list` / `rounds` / `show` / `candidates` / `diff` / `compare` / `export`, `select`, `apply`, `judge`, `recover`, `close`, `prune` `RoomStore.member_sessions` (and through these `RoomCandidateStore.load`, `_parse_sessions_json`, `selection_record_status`, `room_outcome_status`, `round_provenance_status`, `round_conversation_status`); the standalone `apply` / `recover` refusal and skip for room-bound artifacts; a HEAD `room round` on a released room (`branch-from` resuming the released sessions on the released base patch, `fresh`, a first round on a draft, and the `continue-own` refusal when the released workspaces are gone); `room attach` over the released member log paths (tmux faked); the `apply --verify` settings notices; raw export (`--include` preview and confirm); the TUI Rooms tab's readers and pure renders (`build_room_evidence`, `render_room`, `render_round_table`, and the Diff, Output and Details views), not the Textual app | `test_frozen_rooms.py` (v0.1.2, v0.2.0, v0.3.0) | -| Workflows | stored `spec_json`, manifest `schema_version` 1 and 3 with accounting, node `result.json` and `commit.json`; workflow-origin run artifacts (v0.2.0 on) | `WorkflowStore.load`, bundle verification; for workflow-origin artifacts, the approval check at `apply` | not yet | +| Workflows | the workflow database (`workflows.db`): rows with `spec_json`, manifest `schema_version` 3 with `calls_used`, events, and from v0.3.0 the lineage columns and `file`/`revision` origins; per workflow `spec.json`, planner output, node bundles (`result.json`, `winner.patch`, `commit.json` v3) and stream logs; workflow-origin run artifacts, approved or not, with `plan.final_agent` from v0.3.0. No release wrote manifest v1 or v2 (both normalize to v3 before the insert); those readers stay covered by `test_workflow_durable.py::test_marked_v1_migration_preserves_exact_count_and_control_fields` and `test_workflow.py::test_a_workflow_whose_call_accounting_is_ambiguous_is_refused` | `workflow list` / `show` (`--prompts`) / `status` / `logs` / `lineage` / `export` / `cancel` / `prune`, a dead release-written lease recovered by `show` and `status`, `WorkflowStore.events` and `read_node_bundle`; for the final artifact `diff`, `runs show`, `apply` (approved, after a prune, approval healed from the database, refused for a workflow that never completed, `no_changes`), the standalone `recover` skip. A final artifact naming another run, and an unreadable store: `test_cli.py::test_apply_refuses_an_artifact_the_named_workflow_did_not_finalize` and `test_apply_guard_fails_closed_when_the_workflow_store_cannot_be_read`. Not yet: a HEAD `run` or `resume` of a released workflow, `apply --verify` environment narrowing, control-plan (toolgraph) workflows, recovery of an interrupted workflow-origin apply | `test_frozen_workflows.py` (v0.2.0, v0.3.0), partly | | Sessions and project | isolated-session documents and their checkpoint run artifacts, project marker `.syncmill/project.json` | `IsolatedSessionStore.load` / `list`, `_marker_project_id`; the artifact readers for checkpoint runs | not yet | | Board | item rows and their evidence and inputs, decisions | `_row_to_item`, `_load_evidence`, `_row_to_decision` | not yet | | Memory backends | Mem0 and LangGraph stored payloads | the backends' read paths | not yet | diff --git a/tests/fixtures/persisted/workflows/freeze.py b/tests/fixtures/persisted/workflows/freeze.py new file mode 100644 index 0000000..8cd7f57 --- /dev/null +++ b/tests/fixtures/persisted/workflows/freeze.py @@ -0,0 +1,473 @@ +"""Produce the frozen workflow documents under this directory (#271). + +Run once per release, with THAT release's interpreter and source, never the +current one. The output directory must not exist; frozen fixtures are never +regenerated. + + git worktree add --detach /tmp/wt-v0.3.0 v0.3.0 + (cd /tmp/wt-v0.3.0 && uv sync --frozen --no-dev) + python tests/fixtures/persisted/workflows/freeze.py \\ + --release-root /tmp/wt-v0.3.0 --out tests/fixtures/persisted/workflows/v0.3.0 + +Each scenario is one project with its own workflow database. The released +CLI makes every scenario (`workflow plan`, `run`, `cancel`, `status`, and +v0.3.0's `create` and `revise`) against a fake `codex` (the planner and the +`draft` node) and a fake `kimi` (the final `fix` node). Workflow agents run +with an environment allowlist, so the fakes take their behaviour from a file +under HOME, which the allowlist keeps. + +`workflow run` launches a durable job whose worker owns the workflow lease. +Three states need that worker to die: +- `running`: SIGKILLed while the final node's agent works (`draft` is done). +- `unapproved-completed`: exits inside `approve_workflow_run`, after the + fenced completion commit and before the approval is written. +- `unapproved-running`: exits right after the final artifact is published, + before the completion commit. +The two exits are hooks loaded into the worker's own interpreter through a +`.pth` file this script puts in the release venv for the run and removes +afterwards; they act only while HOME holds a `crash-at` file. + +The project's state directory (`//`: the workflow +database, workflow directories and any workflow-origin run artifact) is +copied byte for byte once every writer has stopped, including any WAL a +killed process leaves. Workspaces and TUI job records are not copied. +`bindings.json` records the values bound to the generating machine. +""" + +from __future__ import annotations + +import argparse +import fcntl +import json +import os +import shutil +import signal +import sqlite3 +import subprocess +import sys +import tempfile +import time +from pathlib import Path + +TREE = {"calc.py": "def add(a, b):\n return a - b\n"} +GATE = ["python3 -c \"from calc import add; assert add(2, 3) == 5\""] +SPEC = { + "schema_version": 1, + "goal": "fix add", + "final_node": "fix", + "nodes": [ + {"id": "draft", "agent": "codex", "prompt": "draft the fix"}, + {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}, + ], +} +REVISED = {**SPEC, "goal": "fix add, revised"} + +# HOME/fake-control.json maps the binary to one behaviour: fix (writes a + b), +# wrong (a * b, fails the gate), none (changes nothing), fail (exits 1), hang. +# The planner call (codex, prompt naming final_node) answers with the spec. +FAKE_AGENT = '''\ +import json, os, sys, time +from pathlib import Path +BINARY = os.path.basename(sys.argv[0]) +args = sys.argv[1:] +if args[:2] == ["login", "status"] or args[:1] in (["--version"], ["--help"]): + print(BINARY + " (fake) - logged in"); sys.exit(0) +home = Path(os.environ["HOME"]) +control = json.loads((home / "fake-control.json").read_text()) +if BINARY == "codex" and "final_node" in " ".join(args): + message = json.dumps(control["spec"]) +else: + plan = control.get(BINARY, "fix") + if plan == "hang": + marker = home / "started" / BINARY + marker.parent.mkdir(exist_ok=True) + marker.with_suffix(".tmp").write_text(str(os.getpid())) + os.replace(marker.with_suffix(".tmp"), marker) + time.sleep(300); sys.exit(1) + if plan == "fail": + sys.stderr.write(BINARY + " (fake): refused\\n"); sys.exit(1) + body = {"fix": "a + b", "wrong": "a * b"}.get(plan) + if body: + Path("calc.py").write_text("def add(a, b):\\n return " + body + "\\n", encoding="utf-8") + message = BINARY + " (fake) " + plan + "." +if BINARY == "codex": + events = [ + {"type": "thread.started", "thread_id": "fake-codex-thread"}, + {"type": "item.completed", "item": {"type": "agent_message", "text": message}}, + {"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}}, + ] +else: + events = [ + {"role": "assistant", "content": message}, + {"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", + "content": "To resume this session: kimi -r fake-kimi-session"}, + ] +for event in events: + print(json.dumps(event)) +''' + +# Loaded at interpreter start by the .pth below, in every process of the +# release venv. HOME/fake-host pins the host name the release records in a +# lease owner (so no real machine name is frozen); HOME/crash-at names a +# crash point. Without those files it does nothing. +CRASH_HOOK = '''\ +import os +from pathlib import Path + + +def _install(): + home = Path(os.environ.get("HOME", "/nonexistent")) + host = home / "fake-host" + if host.is_file(): + import socket + name = host.read_text().strip() + socket.gethostname = lambda: name + flag = home / "crash-at" + if not flag.is_file(): + return + point = flag.read_text().strip() + from syncmill.artifacts import ArtifactStore + + if point == "approve": + def approve_workflow_run(self, *args, **kwargs): + os._exit(0) + ArtifactStore.approve_workflow_run = approve_workflow_run + elif point == "publish": + original = ArtifactStore.save + + def save(self, *args, **kwargs): + saved = original(self, *args, **kwargs) + if str(kwargs.get("run_id", "")).endswith("-final"): + os._exit(0) + return saved + ArtifactStore.save = save + + +_install() +''' +HOOK_MODULE = "_syncmill_freeze_hook" +FROZEN_HOST = "frozen-host" + +GIT_ENV = { + "GIT_AUTHOR_NAME": "frozen", "GIT_AUTHOR_EMAIL": "frozen@example.invalid", + "GIT_COMMITTER_NAME": "frozen", "GIT_COMMITTER_EMAIL": "frozen@example.invalid", + "GIT_AUTHOR_DATE": "2026-01-01T00:00:00Z", "GIT_COMMITTER_DATE": "2026-01-01T00:00:00Z", +} + + +def _site_packages(python: Path) -> Path: + return Path(subprocess.run( + [str(python), "-c", "import sysconfig; print(sysconfig.get_paths()['purelib'])"], + check=True, capture_output=True, text=True, + ).stdout.strip()) + + +def _lock_free(path: Path) -> bool: + """Whether no process holds an flock on ``path`` (absent counts as free).""" + try: + handle = path.open("rb") + except FileNotFoundError: + return True + with handle: + try: + fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError: + return False + fcntl.flock(handle, fcntl.LOCK_UN) + return True + + +def _alive(pid: int) -> bool: + try: + os.kill(pid, 0) + except ProcessLookupError: + return False + except PermissionError: + return True + return True + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--release-root", type=Path, required=True) + parser.add_argument("--out", type=Path, required=True) + args = parser.parse_args() + release = args.release_root.resolve() + out = args.out.resolve() + if out.exists(): + sys.exit(f"{out} exists; frozen fixtures are never regenerated") + python = release / ".venv" / "bin" / "python" + syncmill = release / ".venv" / "bin" / "syncmill" + version = subprocess.run( + [str(python), "-c", "import importlib.metadata as m; print(m.version('syncmill'))"], + check=True, capture_output=True, text=True, + ).stdout.strip() + site = _site_packages(python) + hook_files = [site / f"{HOOK_MODULE}.py", site / f"zz{HOOK_MODULE}.pth"] + if any(path.exists() for path in hook_files): + sys.exit(f"{site} already holds the freeze hook") + + # The job manager's control socket lives in XDG_RUNTIME_DIR; a socket + # path may not exceed ~104 bytes, so it gets a short directory of its own. + runtime = Path(tempfile.mkdtemp(prefix="smwf.", dir="/tmp")) + try: + hook_files[0].write_text(CRASH_HOOK, encoding="utf-8") + hook_files[1].write_text(f"import {HOOK_MODULE}\n", encoding="utf-8") + with tempfile.TemporaryDirectory() as tmp: + _generate(Path(tmp).resolve(), runtime, syncmill, version, out) + finally: + for path in hook_files: + path.unlink(missing_ok=True) + shutil.rmtree(runtime, ignore_errors=True) + + +def _generate(box: Path, runtime: Path, syncmill: Path, version: str, out: Path) -> None: + repo, home, state, fakebin = (box / n for n in ("repo", "home", "state", "fakebin")) + for folder in (repo, home, state, fakebin): + folder.mkdir() + (home / "fake-host").write_text(FROZEN_HOST) + for binary in ("codex", "kimi"): + (fakebin / binary).write_text(f"#!{sys.executable}\n" + FAKE_AGENT, encoding="utf-8") + (fakebin / binary).chmod(0o755) + (fakebin / "python3").symlink_to(sys.executable) + base_env = { + "HOME": str(home), + "PATH": os.pathsep.join([str(fakebin), "/usr/bin", "/bin"]), + "TMPDIR": str(box), + "XDG_RUNTIME_DIR": str(runtime), + "GIT_CONFIG_NOSYSTEM": "1", + "PYTHONDONTWRITEBYTECODE": "1", + "SYNCMILL_AGENT_PRIORITY": json.dumps(["codex", "kimi-code"]), + "SYNCMILL_CLAUDE__ENABLED": "false", + "SYNCMILL_RUNS__ROOT": str(state / "runs"), + "SYNCMILL_WORKSPACE__ROOT": str(state / "workspaces"), + "SYNCMILL_MEMORY__DB_PATH": str(state / "memory" / "memtomem.db"), + "SYNCMILL_MEMORY__MEMORY_DIR": str(state / "memory" / "files"), + "SYNCMILL_BREAKER__STATE_PATH": str(state / "breaker.json"), + "SYNCMILL_TUI__STATE_DIR": str(state / "tui"), + **GIT_ENV, + } + + def git(*argv: str) -> None: + subprocess.run(["git", *argv], cwd=repo, env=base_env, check=True, + capture_output=True) + + git("init", "-q", "-b", "main") + for name, text in TREE.items(): + (repo / name).write_text(text, encoding="utf-8") + git("add", "-A") + git("commit", "-q", "-m", "frozen tree") + + out.mkdir(parents=True) + bindings: dict = { + "release": version, + "box_root": str(box), + "hostname": FROZEN_HOST, + "tree": TREE, + "spec": SPEC, + "scenarios": {}, + } + + class Scenario: + def __init__(self, name: str, *, gate: bool = True, **control: str): + self.name = name + self.project = f"frozen-workflows-{name}" + self.env = {**base_env, "SYNCMILL_PROJECT__ID": self.project} + if gate: + self.env["SYNCMILL_GATE__COMMANDS"] = json.dumps(GATE) + self.control = {"spec": SPEC, **control} + self.workflow_ids: list[str] = [] + + @property + def project_dir(self) -> Path: + return state / "runs" / self.project + + def prepare(self, crash_at: str | None = None) -> None: + (home / "fake-control.json").write_text(json.dumps(self.control)) + flag = home / "crash-at" + flag.unlink(missing_ok=True) + if crash_at: + flag.write_text(crash_at) + shutil.rmtree(home / "started", ignore_errors=True) + + def workflow(self, *argv: str, ok: bool = True) -> str: + done = subprocess.run( + [str(syncmill), "workflow", *argv], cwd=repo, env=self.env, + capture_output=True, text=True, + ) + if ok and done.returncode != 0: + sys.exit(f"{self.name}: workflow {argv}: {done.stdout}{done.stderr}") + return done.stdout + done.stderr + + def newest_workflow(self) -> str: + listing = self.workflow("list") + ids = [line.split()[0] for line in listing.splitlines() + if line.startswith("wf-")] + new = [wf for wf in ids if wf not in self.workflow_ids] + if len(new) != 1: + sys.exit(f"{self.name}: expected one new workflow, found {new}") + self.workflow_ids.append(new[0]) + return new[0] + + def plan(self) -> str: + self.prepare() + self.workflow("plan", "fix add", "--planner", "codex") + return self.newest_workflow() + + def settle(sc: Scenario, timeout: float = 60) -> None: + """Wait until no process holds the workflow or job locks.""" + locks = [*sc.project_dir.glob("workflows/.locks/*.lock"), + *(state / "tui").glob("*/locks/*.lock")] + deadline = time.monotonic() + timeout + while not all(_lock_free(lock) for lock in locks): + if time.monotonic() > deadline: + sys.exit(f"{sc.name}: a writer still holds a lock") + time.sleep(0.2) + + def freeze(sc: Scenario) -> None: + settle(sc) + source = sc.project_dir + target = out / sc.name + shutil.copytree(source, target) + # Nothing that a digest covers may carry this machine's paths; only + # the named binding locations may. + for path in target.rglob("*"): + if path.is_file() and path.suffix in {".json", ".patch", ".txt", ".log"}: + if str(box).encode() in path.read_bytes() and path.name != "manifest.json" \ + and "applications" not in path.parts: + sys.exit(f"{sc.name}: {path} carries the generating path") + # Bindings from a scratch copy, so a crash's WAL is seen and the + # frozen files are never opened. + scratch = box / "bindings-read" + shutil.rmtree(scratch, ignore_errors=True) + shutil.copytree(source / "workflows", scratch) + with sqlite3.connect(scratch / "workflows.db") as conn: + conn.row_factory = sqlite3.Row + rows = [dict(row) for row in conn.execute( + "SELECT * FROM workflows ORDER BY created_at, workflow_id" + )] + conn.close() + bindings["scenarios"][sc.name] = { + "project_id": sc.project, + "workflow_ids": [row["workflow_id"] for row in rows], + "states": {row["workflow_id"]: row["state"] for row in rows}, + "source_fingerprints": sorted({row["source_fingerprint"] for row in rows}), + "lease_owners": sorted({row["lease_owner"] for row in rows if row.get("lease_owner")}), + "final_run_ids": sorted({row["final_run_id"] for row in rows if row["final_run_id"]}), + "run_artifacts": sorted(p.name for p in (source / "runs").glob("*/") + if p.is_dir()) if (source / "runs").is_dir() else [], + "wal": sorted(str(p.relative_to(source)) for p in source.rglob("*-wal")), + } + + def job_state(workflow_id: str) -> dict: + for spec in (state / "tui").glob("*/jobs/*/spec.json"): + if workflow_id in spec.read_text(): + state_file = spec.with_name("state.json") + if state_file.is_file(): + return json.loads(state_file.read_text()) + return {} + + def run_until(sc: Scenario, workflow_id: str, marker: str) -> None: + """Start a detached run, wait for an agent's start marker, then kill + the job's worker group and the agent's own group.""" + sc.workflow("run", workflow_id, "--detach") + started = home / "started" / marker + deadline = time.monotonic() + 120 + while not started.exists(): + if time.monotonic() > deadline: + sys.exit(f"{sc.name}: the {marker} agent never started") + time.sleep(0.1) + job = job_state(workflow_id) + if not job.get("pgid"): + sys.exit(f"{sc.name}: no job record for {workflow_id}") + os.killpg(int(job["pgid"]), signal.SIGKILL) + agent = int(started.read_text()) + try: + os.killpg(os.getpgid(agent), signal.SIGKILL) + except ProcessLookupError: + pass + deadline = time.monotonic() + 60 + while _alive(int(job["pid"])) or _alive(agent): + if time.monotonic() > deadline: + sys.exit(f"{sc.name}: the worker or its agent survived SIGKILL") + time.sleep(0.1) + + # ── the lifecycle ──────────────────────────────────────────────────── + sc = Scenario("planned") + sc.plan() + freeze(sc) + + sc = Scenario("completed") + workflow_id = sc.plan() + sc.workflow("run", workflow_id) + freeze(sc) + + # No gate, and neither node changes anything: an empty final patch. + sc = Scenario("no-changes", gate=False, codex="none", kimi="none") + workflow_id = sc.plan() + sc.workflow("run", workflow_id) + freeze(sc) + + # The final node's change fails the gate: a failing report, no artifact. + sc = Scenario("gate-failed", kimi="wrong") + workflow_id = sc.plan() + sc.workflow("run", workflow_id, ok=False) + freeze(sc) + + # The final node's only attempt fails. + sc = Scenario("failed", kimi="fail") + workflow_id = sc.plan() + sc.workflow("run", workflow_id, ok=False) + freeze(sc) + + sc = Scenario("cancelled") + workflow_id = sc.plan() + sc.workflow("cancel", workflow_id) + freeze(sc) + + # The worker dies while the final node's agent works; then the + # release's own `status` recovers the dead lease. + sc = Scenario("running", kimi="hang") + workflow_id = sc.plan() + sc.prepare() + run_until(sc, workflow_id, "kimi") + freeze(sc) + sc.name = "recovery-required" + sc.workflow("status", workflow_id) + freeze(sc) + + sc = Scenario("unapproved-completed") + workflow_id = sc.plan() + sc.prepare(crash_at="approve") + sc.workflow("run", workflow_id, ok=False) + (home / "crash-at").unlink() + freeze(sc) + + sc = Scenario("unapproved-running") + workflow_id = sc.plan() + sc.prepare(crash_at="publish") + sc.workflow("run", workflow_id, ok=False) + (home / "crash-at").unlink() + freeze(sc) + + if version != "0.2.0": + # A plan from a document, then a revision of it: one lineage, two + # plan versions, no planner call. + sc = Scenario("created") + document = box / "spec.json" + document.write_text(json.dumps(SPEC)) + sc.prepare() + sc.workflow("create", "--file", str(document)) + parent = sc.newest_workflow() + freeze(sc) + sc.name = "revised" + document.write_text(json.dumps(REVISED)) + sc.workflow("revise", parent, "--file", str(document)) + sc.newest_workflow() + freeze(sc) + + (out / "bindings.json").write_text(json.dumps(bindings, indent=2) + "\n") + + +if __name__ == "__main__": + main() diff --git a/tests/fixtures/persisted/workflows/v0.2.0/bindings.json b/tests/fixtures/persisted/workflows/v0.2.0/bindings.json new file mode 100644 index 0000000..c4be9b1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/bindings.json @@ -0,0 +1,208 @@ +{ + "release": "0.2.0", + "box_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpis4bisz9", + "hostname": "frozen-host", + "tree": { + "calc.py": "def add(a, b):\n return a - b\n" + }, + "spec": { + "schema_version": 1, + "goal": "fix add", + "final_node": "fix", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix" + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ] + } + ] + }, + "scenarios": { + "planned": { + "project_id": "frozen-workflows-planned", + "workflow_ids": [ + "wf-5dae0f0d596348b5" + ], + "states": { + "wf-5dae0f0d596348b5": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "completed": { + "project_id": "frozen-workflows-completed", + "workflow_ids": [ + "wf-55da1c5cde7b4e47" + ], + "states": { + "wf-55da1c5cde7b4e47": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-55da1c5cde7b4e47-final" + ], + "run_artifacts": [ + "wf-55da1c5cde7b4e47-final" + ], + "wal": [] + }, + "no-changes": { + "project_id": "frozen-workflows-no-changes", + "workflow_ids": [ + "wf-cde618c312b34866" + ], + "states": { + "wf-cde618c312b34866": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-cde618c312b34866-final" + ], + "run_artifacts": [ + "wf-cde618c312b34866-final" + ], + "wal": [] + }, + "gate-failed": { + "project_id": "frozen-workflows-gate-failed", + "workflow_ids": [ + "wf-62a8b48d99e44279" + ], + "states": { + "wf-62a8b48d99e44279": "failed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "failed": { + "project_id": "frozen-workflows-failed", + "workflow_ids": [ + "wf-d99be4e0722d412f" + ], + "states": { + "wf-d99be4e0722d412f": "failed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "cancelled": { + "project_id": "frozen-workflows-cancelled", + "workflow_ids": [ + "wf-fa435cefbe524d19" + ], + "states": { + "wf-fa435cefbe524d19": "cancelled" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "running": { + "project_id": "frozen-workflows-running", + "workflow_ids": [ + "wf-a5e80d4b34c1400b" + ], + "states": { + "wf-a5e80d4b34c1400b": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "46320@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "recovery-required": { + "project_id": "frozen-workflows-running", + "workflow_ids": [ + "wf-a5e80d4b34c1400b" + ], + "states": { + "wf-a5e80d4b34c1400b": "recovery_required" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "unapproved-completed": { + "project_id": "frozen-workflows-unapproved-completed", + "workflow_ids": [ + "wf-10ba8b77464248b9" + ], + "states": { + "wf-10ba8b77464248b9": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-10ba8b77464248b9-final" + ], + "run_artifacts": [ + "wf-10ba8b77464248b9-final" + ], + "wal": [] + }, + "unapproved-running": { + "project_id": "frozen-workflows-unapproved-running", + "workflow_ids": [ + "wf-21ef6ff85c3a4fb9" + ], + "states": { + "wf-21ef6ff85c3a4fb9": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "46553@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [ + "wf-21ef6ff85c3a4fb9-final" + ], + "wal": [] + } + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/.locks/wf-fa435cefbe524d19.lock b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/.locks/wf-fa435cefbe524d19.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/wf-fa435cefbe524d19/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/workflows.db new file mode 100644 index 0000000..fb3aa93 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/cancelled/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/manifest.json b/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/manifest.json new file mode 100644 index 0000000..8c66f22 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/manifest.json @@ -0,0 +1,41 @@ +{ + "schema_version": 1, + "run_id": "wf-55da1c5cde7b4e47-final", + "project_id": "frozen-workflows-completed", + "parent_run_id": null, + "created_at": "2026-11-09T10:19:42.848946+00:00", + "updated_at": "2026-11-09T10:19:42.859356+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpis4bisz9/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "40c213304d825b5d2698d0f0e0d1869a1d8104a4", + "task_id": "04da33b2-3bb3-4b0b-aaec-903436986d12", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-55da1c5cde7b4e47", + "node_id": "fix", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "065e692b354df002d41777b3d7641e79578ecaf3fe701ab159a6eb075ea61978", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved": { + "workflow_id": "wf-55da1c5cde7b4e47", + "run_id": "wf-55da1c5cde7b4e47-final", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved_at": "2026-11-09T10:19:42.859351+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/completed/runs/wf-55da1c5cde7b4e47-final/result.json b/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/result.json new file mode 100644 index 0000000..d310f81 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/result.json @@ -0,0 +1,7 @@ +{ + "status": "completed", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.08 +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/runs/wf-55da1c5cde7b4e47-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/completed/workflows/.locks/wf-55da1c5cde7b4e47.lock b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/.locks/wf-55da1c5cde7b4e47.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/commit.json new file mode 100644 index 0000000..bdae968 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-55da1c5cde7b4e47", "node_id": "draft", "lease_id": "bd2ffa83a02640f1bb53cfe54fb73729", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "c3d36f0c37e02e76fa308f3845bb482a30b7f32722cead55f4daec19378110e3"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/result.json new file mode 100644 index 0000000..5edc5f3 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-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-55da1c5cde7b4e47", + "node_id": "draft", + "lease_id": "bd2ffa83a02640f1bb53cfe54fb73729", + "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/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/bd2ffa83a02640f1bb53cfe54fb73729-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/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/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/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/commit.json new file mode 100644 index 0000000..3336b0c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-55da1c5cde7b4e47", "node_id": "fix", "lease_id": "bd2ffa83a02640f1bb53cfe54fb73729", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "065e692b354df002d41777b3d7641e79578ecaf3fe701ab159a6eb075ea61978"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/result.json new file mode 100644 index 0000000..391c4a6 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-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.08, + "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-55da1c5cde7b4e47", + "node_id": "fix", + "lease_id": "bd2ffa83a02640f1bb53cfe54fb73729", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/bd2ffa83a02640f1bb53cfe54fb73729-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/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/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/completed/workflows/wf-55da1c5cde7b4e47/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/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/completed/workflows/wf-55da1c5cde7b4e47/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/wf-55da1c5cde7b4e47/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/completed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/workflows.db new file mode 100644 index 0000000..2b8d98e Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/completed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/.locks/wf-d99be4e0722d412f.lock b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/.locks/wf-d99be4e0722d412f.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json new file mode 100644 index 0000000..fdd8a05 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-d99be4e0722d412f", "node_id": "draft", "lease_id": "1e2b8ee9522b4f899ec4381d5e68ca65", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "5bf65992498c42ace5753528d20bf207fec84b7e5132bbc8977782546c76b43f"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json new file mode 100644 index 0000000..ff2e2d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-d99be4e0722d412f", + "node_id": "draft", + "lease_id": "1e2b8ee9522b4f899ec4381d5e68ca65", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json new file mode 100644 index 0000000..4d95bc3 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-d99be4e0722d412f", "node_id": "fix", "lease_id": "1e2b8ee9522b4f899ec4381d5e68ca65", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "73455018bde6a139f65a690d9a62b4eeb0388a9cfd68c3790ae6ed02ee5227f0"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json new file mode 100644 index 0000000..8f6a2a0 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/result.json @@ -0,0 +1,19 @@ +{ + "status": "failed", + "agent_id": "kimi-code", + "output": "", + "error": "kimi (fake): refused", + "exit_code": 1, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-d99be4e0722d412f", + "node_id": "fix", + "lease_id": "1e2b8ee9522b4f899ec4381d5e68ca65", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/1e2b8ee9522b4f899ec4381d5e68ca65-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/stream.log new file mode 100644 index 0000000..899289d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/nodes/fix/stream.log @@ -0,0 +1 @@ +kimi (fake): refused diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/wf-d99be4e0722d412f/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/workflows.db new file mode 100644 index 0000000..b79a436 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/failed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/.locks/wf-62a8b48d99e44279.lock b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/.locks/wf-62a8b48d99e44279.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json new file mode 100644 index 0000000..8706698 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-62a8b48d99e44279", "node_id": "draft", "lease_id": "bc5e7b6d629248399c44c60a4964a5c3", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "cf2164b8d489b4fb97731fd6995909e190587b501ad04a5b080a06cf58f4cddb"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/result.json new file mode 100644 index 0000000..35d35f9 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-62a8b48d99e44279", + "node_id": "draft", + "lease_id": "bc5e7b6d629248399c44c60a4964a5c3", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json new file mode 100644 index 0000000..99405ac --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-62a8b48d99e44279", "node_id": "fix", "lease_id": "bc5e7b6d629248399c44c60a4964a5c3", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "73504d91039ea4579d212c81b1e0799ecefd7612a13c4e07b52c76e6ceaa3d90"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/result.json new file mode 100644 index 0000000..ccaffae --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/result.json @@ -0,0 +1,33 @@ +{ + "status": "failed", + "agent_id": "kimi-code", + "output": "kimi (fake) wrong.", + "error": "quality gate failed: 0/1 checks passed; failed: python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": { + "passed": false, + "score": 0.0, + "checks": [ + { + "command": "python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "passed": false, + "exit_code": 1, + "output": "Traceback (most recent call last):\n File \"\", line 1, in \n from calc import add; assert add(2, 3) == 5\n ^^^^^^^^^^^^^^\nAssertionError", + "duration_seconds": 0.02 + } + ] + }, + "patch_sha256": "93a267aca73ce9b58f03c7393a75925def1745c1921d6862043aecb70be4196d", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-62a8b48d99e44279", + "node_id": "fix", + "lease_id": "bc5e7b6d629248399c44c60a4964a5c3", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch new file mode 100644 index 0000000..b78cd53 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/bc5e7b6d629248399c44c60a4964a5c3-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..7f32081 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a * b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/stream.log new file mode 100644 index 0000000..f930192 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) wrong."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/wf-62a8b48d99e44279/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/workflows.db new file mode 100644 index 0000000..0db2420 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/gate-failed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/manifest.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/manifest.json new file mode 100644 index 0000000..4716541 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/manifest.json @@ -0,0 +1,39 @@ +{ + "schema_version": 1, + "run_id": "wf-cde618c312b34866-final", + "project_id": "frozen-workflows-no-changes", + "parent_run_id": null, + "created_at": "2026-11-09T10:19:46.677959+00:00", + "updated_at": "2026-11-09T10:19:46.687985+00:00", + "state": "no_changes", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpis4bisz9/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "4513f9a69dcb1d2a84110be7014f94f772809bb6", + "task_id": "43f96150-8f6f-4635-8951-76bd591a2041", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-cde618c312b34866", + "node_id": "fix", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "d7b327555dd235ee9693176d3e000b172284eda6e4042a1bd7c8f763b5af02cb", + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "approved": { + "workflow_id": "wf-cde618c312b34866", + "run_id": "wf-cde618c312b34866-final", + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "approved_at": "2026-11-09T10:19:46.687980+00:00" + } + }, + "agent_id": "kimi-code", + "files_changed": [], + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "provider_session_id": null, + "conversation_id": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/result.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/result.json new file mode 100644 index 0000000..abe0019 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/result.json @@ -0,0 +1,7 @@ +{ + "status": "completed", + "output": "kimi (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02 +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/runs/wf-cde618c312b34866-final/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/.locks/wf-cde618c312b34866.lock b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/.locks/wf-cde618c312b34866.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json new file mode 100644 index 0000000..4987ba0 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-cde618c312b34866", "node_id": "draft", "lease_id": "2d1000d7a24f4a358e0f8efe65beb782", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "cbf6f4d323e39f30ec0b1ba490ba0892e85f6133b97267bfccb7600969498eff"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/result.json new file mode 100644 index 0000000..17cb5d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/result.json @@ -0,0 +1,25 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-cde618c312b34866", + "node_id": "draft", + "lease_id": "2d1000d7a24f4a358e0f8efe65beb782", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/stream.log new file mode 100644 index 0000000..88a7e4c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) none."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json new file mode 100644 index 0000000..23b44cc --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-cde618c312b34866", "node_id": "fix", "lease_id": "2d1000d7a24f4a358e0f8efe65beb782", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "d7b327555dd235ee9693176d3e000b172284eda6e4042a1bd7c8f763b5af02cb"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/result.json new file mode 100644 index 0000000..b4d22a3 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/result.json @@ -0,0 +1,19 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-cde618c312b34866", + "node_id": "fix", + "lease_id": "2d1000d7a24f4a358e0f8efe65beb782", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/2d1000d7a24f4a358e0f8efe65beb782-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/stream.log new file mode 100644 index 0000000..f07794d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) none."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/wf-cde618c312b34866/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/workflows.db new file mode 100644 index 0000000..dd9829d Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/no-changes/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/.locks/wf-5dae0f0d596348b5.lock b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/.locks/wf-5dae0f0d596348b5.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/wf-5dae0f0d596348b5/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/workflows.db new file mode 100644 index 0000000..83989dc Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/planned/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/.locks/wf-a5e80d4b34c1400b.lock b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/.locks/wf-a5e80d4b34c1400b.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json new file mode 100644 index 0000000..2f5816b --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-a5e80d4b34c1400b", "node_id": "draft", "lease_id": "cd65d7ffec9c444ebad6e7dfc7917d09", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "dbf8bb72f4bd941ee3c5bebc09a7950a2718ce2f798c1dede915a1a87d0bb374"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json new file mode 100644 index 0000000..d3dbefa --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-a5e80d4b34c1400b", + "node_id": "draft", + "lease_id": "cd65d7ffec9c444ebad6e7dfc7917d09", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/wf-a5e80d4b34c1400b/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/workflows.db new file mode 100644 index 0000000..24100ef Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/recovery-required/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/.locks/wf-a5e80d4b34c1400b.lock b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/.locks/wf-a5e80d4b34c1400b.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json new file mode 100644 index 0000000..2f5816b --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-a5e80d4b34c1400b", "node_id": "draft", "lease_id": "cd65d7ffec9c444ebad6e7dfc7917d09", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "dbf8bb72f4bd941ee3c5bebc09a7950a2718ce2f798c1dede915a1a87d0bb374"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json new file mode 100644 index 0000000..d3dbefa --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-a5e80d4b34c1400b", + "node_id": "draft", + "lease_id": "cd65d7ffec9c444ebad6e7dfc7917d09", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/cd65d7ffec9c444ebad6e7dfc7917d09-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/wf-a5e80d4b34c1400b/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/workflows.db new file mode 100644 index 0000000..c8dcfb6 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/running/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/manifest.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/manifest.json new file mode 100644 index 0000000..cc1ecbd --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/manifest.json @@ -0,0 +1,35 @@ +{ + "schema_version": 1, + "run_id": "wf-10ba8b77464248b9-final", + "project_id": "frozen-workflows-unapproved-completed", + "parent_run_id": null, + "created_at": "2026-11-09T10:20:01.638879+00:00", + "updated_at": "2026-11-09T10:20:01.639658+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpis4bisz9/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "6f229019298b9256acc5c16ce454122af495585b", + "task_id": "445c441b-05ec-4710-a46e-3a4e81ae5db1", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-10ba8b77464248b9", + "node_id": "fix", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "1428f4eaf60a749486ad9d8512a95deb97a8de1c03ead8ac0e39d27f001d2d2b", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba" + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/result.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-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/unapproved-completed/runs/wf-10ba8b77464248b9-final/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/runs/wf-10ba8b77464248b9-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/unapproved-completed/workflows/.locks/wf-10ba8b77464248b9.lock b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/.locks/wf-10ba8b77464248b9.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/commit.json new file mode 100644 index 0000000..4da2ce7 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-10ba8b77464248b9", "node_id": "draft", "lease_id": "2c525cfbb96a4ac9b669b0fd73027347", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "970519bf2ae63b3ed4909a1896de95f5ca00f30359694b644bb22114d91a170f"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/result.json new file mode 100644 index 0000000..e019fa6 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-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-10ba8b77464248b9", + "node_id": "draft", + "lease_id": "2c525cfbb96a4ac9b669b0fd73027347", + "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/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/2c525cfbb96a4ac9b669b0fd73027347-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/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/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/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/commit.json new file mode 100644 index 0000000..aa959c6 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-10ba8b77464248b9", "node_id": "fix", "lease_id": "2c525cfbb96a4ac9b669b0fd73027347", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "1428f4eaf60a749486ad9d8512a95deb97a8de1c03ead8ac0e39d27f001d2d2b"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/result.json new file mode 100644 index 0000000..beba6ef --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-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-10ba8b77464248b9", + "node_id": "fix", + "lease_id": "2c525cfbb96a4ac9b669b0fd73027347", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/2c525cfbb96a4ac9b669b0fd73027347-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/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/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/unapproved-completed/workflows/wf-10ba8b77464248b9/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/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/unapproved-completed/workflows/wf-10ba8b77464248b9/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/wf-10ba8b77464248b9/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/unapproved-completed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/workflows.db new file mode 100644 index 0000000..dc90d3a Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-completed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/manifest.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/manifest.json new file mode 100644 index 0000000..6f0a284 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/manifest.json @@ -0,0 +1,35 @@ +{ + "schema_version": 1, + "run_id": "wf-21ef6ff85c3a4fb9-final", + "project_id": "frozen-workflows-unapproved-running", + "parent_run_id": null, + "created_at": "2026-11-09T10:20:03.631985+00:00", + "updated_at": "2026-11-09T10:20:03.632780+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpis4bisz9/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "87b8b9ac749ba65b1ead4ad014ab35fe950e8c77", + "task_id": "4e485dc1-ec91-4d99-aa69-1b47693301f8", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-21ef6ff85c3a4fb9", + "node_id": "fix", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "480a98dcf21af2846d4f1e95e0d95e02af1a5e9cf5d31e9493af521bb7ac77e2", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba" + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/result.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-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/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/runs/wf-21ef6ff85c3a4fb9-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/unapproved-running/workflows/.locks/wf-21ef6ff85c3a4fb9.lock b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/.locks/wf-21ef6ff85c3a4fb9.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/commit.json new file mode 100644 index 0000000..bc7c151 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-21ef6ff85c3a4fb9", "node_id": "draft", "lease_id": "2103f46d121c4a1589a2a538cf522972", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "d600b8ce469cb0e31fbe94d63e539fdd10afbdd2b2b1c8cd08abeb8d7869d1b4"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/result.json new file mode 100644 index 0000000..48fe14e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-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-21ef6ff85c3a4fb9", + "node_id": "draft", + "lease_id": "2103f46d121c4a1589a2a538cf522972", + "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/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/2103f46d121c4a1589a2a538cf522972-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/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/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/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/commit.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/commit.json new file mode 100644 index 0000000..5fff182 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-21ef6ff85c3a4fb9", "node_id": "fix", "lease_id": "2103f46d121c4a1589a2a538cf522972", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "480a98dcf21af2846d4f1e95e0d95e02af1a5e9cf5d31e9493af521bb7ac77e2"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/result.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/result.json new file mode 100644 index 0000000..a8dd0b0 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-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-21ef6ff85c3a4fb9", + "node_id": "fix", + "lease_id": "2103f46d121c4a1589a2a538cf522972", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/winner.patch b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/2103f46d121c4a1589a2a538cf522972-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/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/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/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/planner-output.txt b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/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/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/spec.json b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/wf-21ef6ff85c3a4fb9/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/unapproved-running/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/workflows.db new file mode 100644 index 0000000..6061345 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.2.0/unapproved-running/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/bindings.json b/tests/fixtures/persisted/workflows/v0.3.0/bindings.json new file mode 100644 index 0000000..e93efde --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/bindings.json @@ -0,0 +1,242 @@ +{ + "release": "0.3.0", + "box_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpl1eck1pe", + "hostname": "frozen-host", + "tree": { + "calc.py": "def add(a, b):\n return a - b\n" + }, + "spec": { + "schema_version": 1, + "goal": "fix add", + "final_node": "fix", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix" + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ] + } + ] + }, + "scenarios": { + "planned": { + "project_id": "frozen-workflows-planned", + "workflow_ids": [ + "wf-dd2f5d4d4a694390" + ], + "states": { + "wf-dd2f5d4d4a694390": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "completed": { + "project_id": "frozen-workflows-completed", + "workflow_ids": [ + "wf-7339a776c4604b99" + ], + "states": { + "wf-7339a776c4604b99": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-7339a776c4604b99-final" + ], + "run_artifacts": [ + "wf-7339a776c4604b99-final" + ], + "wal": [] + }, + "no-changes": { + "project_id": "frozen-workflows-no-changes", + "workflow_ids": [ + "wf-e7d96a830ad54285" + ], + "states": { + "wf-e7d96a830ad54285": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-e7d96a830ad54285-final" + ], + "run_artifacts": [ + "wf-e7d96a830ad54285-final" + ], + "wal": [] + }, + "gate-failed": { + "project_id": "frozen-workflows-gate-failed", + "workflow_ids": [ + "wf-5e5335a66e3e4a5f" + ], + "states": { + "wf-5e5335a66e3e4a5f": "failed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "failed": { + "project_id": "frozen-workflows-failed", + "workflow_ids": [ + "wf-0a91dd8ccd0a4561" + ], + "states": { + "wf-0a91dd8ccd0a4561": "failed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "cancelled": { + "project_id": "frozen-workflows-cancelled", + "workflow_ids": [ + "wf-8182839e1fba4058" + ], + "states": { + "wf-8182839e1fba4058": "cancelled" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "running": { + "project_id": "frozen-workflows-running", + "workflow_ids": [ + "wf-f212e2feb55a4cb8" + ], + "states": { + "wf-f212e2feb55a4cb8": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "47731@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "recovery-required": { + "project_id": "frozen-workflows-running", + "workflow_ids": [ + "wf-f212e2feb55a4cb8" + ], + "states": { + "wf-f212e2feb55a4cb8": "recovery_required" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "unapproved-completed": { + "project_id": "frozen-workflows-unapproved-completed", + "workflow_ids": [ + "wf-17969765182e4464" + ], + "states": { + "wf-17969765182e4464": "completed" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [ + "wf-17969765182e4464-final" + ], + "run_artifacts": [ + "wf-17969765182e4464-final" + ], + "wal": [] + }, + "unapproved-running": { + "project_id": "frozen-workflows-unapproved-running", + "workflow_ids": [ + "wf-2d9b5846bc1b4adf" + ], + "states": { + "wf-2d9b5846bc1b4adf": "running" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [ + "48048@frozen-host" + ], + "final_run_ids": [], + "run_artifacts": [ + "wf-2d9b5846bc1b4adf-final" + ], + "wal": [] + }, + "created": { + "project_id": "frozen-workflows-created", + "workflow_ids": [ + "wf-715ce7479b854ad2" + ], + "states": { + "wf-715ce7479b854ad2": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + }, + "revised": { + "project_id": "frozen-workflows-created", + "workflow_ids": [ + "wf-715ce7479b854ad2", + "wf-22e223bfd8e74689" + ], + "states": { + "wf-715ce7479b854ad2": "planned", + "wf-22e223bfd8e74689": "planned" + }, + "source_fingerprints": [ + "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef" + ], + "lease_owners": [], + "final_run_ids": [], + "run_artifacts": [], + "wal": [] + } + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/.locks/wf-8182839e1fba4058.lock b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/.locks/wf-8182839e1fba4058.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/wf-8182839e1fba4058/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/workflows.db new file mode 100644 index 0000000..eacce04 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/cancelled/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/manifest.json b/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/manifest.json new file mode 100644 index 0000000..01964f0 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/manifest.json @@ -0,0 +1,42 @@ +{ + "schema_version": 1, + "run_id": "wf-7339a776c4604b99-final", + "project_id": "frozen-workflows-completed", + "parent_run_id": null, + "created_at": "2026-11-09T10:20:08.900344+00:00", + "updated_at": "2026-11-09T10:20:08.911590+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpl1eck1pe/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "a8a81476f25c1ef93be50b9599a1e4e520309f46", + "task_id": "99740c0f-7f8d-4c49-aaaa-26073706ff82", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-7339a776c4604b99", + "node_id": "fix", + "final_agent": "kimi-code", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "0484b680e96a2af956ae50e550bc069fd4195f808be84ef59185ddf30386a8cd", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved": { + "workflow_id": "wf-7339a776c4604b99", + "run_id": "wf-7339a776c4604b99-final", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "approved_at": "2026-11-09T10:20:08.911581+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/completed/runs/wf-7339a776c4604b99-final/result.json b/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/result.json new file mode 100644 index 0000000..d310f81 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/result.json @@ -0,0 +1,7 @@ +{ + "status": "completed", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.08 +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/runs/wf-7339a776c4604b99-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/completed/workflows/.locks/wf-7339a776c4604b99.lock b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/.locks/wf-7339a776c4604b99.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/commit.json new file mode 100644 index 0000000..b1b8d43 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-7339a776c4604b99", "node_id": "draft", "lease_id": "50e33c3285c94ab8896061bec1c8a5bb", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "455fa6fb41e9691f69d28e9d2807f52876a273b42af45c4151c69bf86233dfd4"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/result.json new file mode 100644 index 0000000..f987422 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-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-7339a776c4604b99", + "node_id": "draft", + "lease_id": "50e33c3285c94ab8896061bec1c8a5bb", + "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/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/50e33c3285c94ab8896061bec1c8a5bb-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/completed/workflows/wf-7339a776c4604b99/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/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/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/commit.json new file mode 100644 index 0000000..0e5375d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-7339a776c4604b99", "node_id": "fix", "lease_id": "50e33c3285c94ab8896061bec1c8a5bb", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "0484b680e96a2af956ae50e550bc069fd4195f808be84ef59185ddf30386a8cd"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/result.json new file mode 100644 index 0000000..ab08fdd --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-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.08, + "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-7339a776c4604b99", + "node_id": "fix", + "lease_id": "50e33c3285c94ab8896061bec1c8a5bb", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/50e33c3285c94ab8896061bec1c8a5bb-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/completed/workflows/wf-7339a776c4604b99/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/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/completed/workflows/wf-7339a776c4604b99/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/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/completed/workflows/wf-7339a776c4604b99/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/wf-7339a776c4604b99/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/completed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/workflows.db new file mode 100644 index 0000000..6c8f5ad Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/completed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/.locks/wf-715ce7479b854ad2.lock b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/.locks/wf-715ce7479b854ad2.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/wf-715ce7479b854ad2/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/wf-715ce7479b854ad2/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/wf-715ce7479b854ad2/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/workflows.db new file mode 100644 index 0000000..a42ec91 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/created/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/.locks/wf-0a91dd8ccd0a4561.lock b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/.locks/wf-0a91dd8ccd0a4561.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/commit.json new file mode 100644 index 0000000..f90d636 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-0a91dd8ccd0a4561", "node_id": "draft", "lease_id": "06221684c085442e8d9a18c7b78dabe1", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "579e7429d976e8c201a9fdbd6c5041f7bf2d2b69b67c441822bd40c7f344c816"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/result.json new file mode 100644 index 0000000..d9ccfdd --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-0a91dd8ccd0a4561", + "node_id": "draft", + "lease_id": "06221684c085442e8d9a18c7b78dabe1", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/06221684c085442e8d9a18c7b78dabe1-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/commit.json new file mode 100644 index 0000000..2e441f4 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-0a91dd8ccd0a4561", "node_id": "fix", "lease_id": "06221684c085442e8d9a18c7b78dabe1", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "8b5adc2a3a23b87acc6b723e25ccee1331ca6b576e3c8887869ad03d7d8cafa4"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/result.json new file mode 100644 index 0000000..92ad19a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/result.json @@ -0,0 +1,19 @@ +{ + "status": "failed", + "agent_id": "kimi-code", + "output": "", + "error": "kimi (fake): refused", + "exit_code": 1, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-0a91dd8ccd0a4561", + "node_id": "fix", + "lease_id": "06221684c085442e8d9a18c7b78dabe1", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/06221684c085442e8d9a18c7b78dabe1-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/stream.log new file mode 100644 index 0000000..899289d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/nodes/fix/stream.log @@ -0,0 +1 @@ +kimi (fake): refused diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/wf-0a91dd8ccd0a4561/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/workflows.db new file mode 100644 index 0000000..717702a Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/failed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/.locks/wf-5e5335a66e3e4a5f.lock b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/.locks/wf-5e5335a66e3e4a5f.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/commit.json new file mode 100644 index 0000000..34aa8ab --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-5e5335a66e3e4a5f", "node_id": "draft", "lease_id": "40c036e29e344451a6dbb8fccc30d95f", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "e41c5eb0d5c1a9788c0dd8ce70052f6c66d274027911a8926d2801b5a89da3ce"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/result.json new file mode 100644 index 0000000..f2f806a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-5e5335a66e3e4a5f", + "node_id": "draft", + "lease_id": "40c036e29e344451a6dbb8fccc30d95f", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/commit.json new file mode 100644 index 0000000..6c0c280 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-5e5335a66e3e4a5f", "node_id": "fix", "lease_id": "40c036e29e344451a6dbb8fccc30d95f", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "968d3a8caf7ad122f3df80f923d0a8573b4d7515b279aa067f405bc2b415e9c8"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/result.json new file mode 100644 index 0000000..f60576f --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/result.json @@ -0,0 +1,33 @@ +{ + "status": "failed", + "agent_id": "kimi-code", + "output": "kimi (fake) wrong.", + "error": "quality gate failed: 0/1 checks passed; failed: python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": { + "passed": false, + "score": 0.0, + "checks": [ + { + "command": "python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "passed": false, + "exit_code": 1, + "output": "Traceback (most recent call last):\n File \"\", line 1, in \n from calc import add; assert add(2, 3) == 5\n ^^^^^^^^^^^^^^\nAssertionError", + "duration_seconds": 0.03 + } + ] + }, + "patch_sha256": "93a267aca73ce9b58f03c7393a75925def1745c1921d6862043aecb70be4196d", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-5e5335a66e3e4a5f", + "node_id": "fix", + "lease_id": "40c036e29e344451a6dbb8fccc30d95f", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch new file mode 100644 index 0000000..b78cd53 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/40c036e29e344451a6dbb8fccc30d95f-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..7f32081 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a * b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/stream.log new file mode 100644 index 0000000..f930192 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) wrong."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/wf-5e5335a66e3e4a5f/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/workflows.db new file mode 100644 index 0000000..4538dee Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/gate-failed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/.apply.lock b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/.apply.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/manifest.json b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/manifest.json new file mode 100644 index 0000000..331b4cc --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/manifest.json @@ -0,0 +1,40 @@ +{ + "schema_version": 1, + "run_id": "wf-e7d96a830ad54285-final", + "project_id": "frozen-workflows-no-changes", + "parent_run_id": null, + "created_at": "2026-11-09T10:20:14.627905+00:00", + "updated_at": "2026-11-09T10:20:14.639312+00:00", + "state": "no_changes", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpl1eck1pe/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "d4ed06249fb89fde37302777af63ac32c74b6971", + "task_id": "e50493b0-a46a-4ae3-a54c-4b742307f19c", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-e7d96a830ad54285", + "node_id": "fix", + "final_agent": "kimi-code", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "1917eb0c4e9a335bdae44d4edda184263d48a2997936614114ec522df3bdca3b", + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "approved": { + "workflow_id": "wf-e7d96a830ad54285", + "run_id": "wf-e7d96a830ad54285-final", + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "approved_at": "2026-11-09T10:20:14.639301+00:00" + } + }, + "agent_id": "kimi-code", + "files_changed": [], + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "provider_session_id": null, + "conversation_id": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/result.json b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/result.json new file mode 100644 index 0000000..abe0019 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/result.json @@ -0,0 +1,7 @@ +{ + "status": "completed", + "output": "kimi (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02 +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/runs/wf-e7d96a830ad54285-final/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/.locks/wf-e7d96a830ad54285.lock b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/.locks/wf-e7d96a830ad54285.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/commit.json new file mode 100644 index 0000000..8cbe407 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-e7d96a830ad54285", "node_id": "draft", "lease_id": "8921c7de4b03464082e8d82766629564", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "68d1d26cb9dc5d00aa45064ec1a7c53c2a55303e455d6110a17bdbfff827f30d"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/result.json new file mode 100644 index 0000000..724df3e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/result.json @@ -0,0 +1,25 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-e7d96a830ad54285", + "node_id": "draft", + "lease_id": "8921c7de4b03464082e8d82766629564", + "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/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/8921c7de4b03464082e8d82766629564-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/stream.log new file mode 100644 index 0000000..88a7e4c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) none."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/commit.json new file mode 100644 index 0000000..9401d1c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-e7d96a830ad54285", "node_id": "fix", "lease_id": "8921c7de4b03464082e8d82766629564", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "1917eb0c4e9a335bdae44d4edda184263d48a2997936614114ec522df3bdca3b"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/result.json new file mode 100644 index 0000000..628c571 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/result.json @@ -0,0 +1,19 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) none.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [], + "gate": null, + "patch_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "patch_bytes": 0, + "bundle_version": 3, + "workflow_id": "wf-e7d96a830ad54285", + "node_id": "fix", + "lease_id": "8921c7de4b03464082e8d82766629564", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/8921c7de4b03464082e8d82766629564-1/winner.patch new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/stream.log new file mode 100644 index 0000000..f07794d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) none."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/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/no-changes/workflows/wf-e7d96a830ad54285/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/wf-e7d96a830ad54285/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/no-changes/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/workflows.db new file mode 100644 index 0000000..6a352cd Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/no-changes/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/.locks/wf-dd2f5d4d4a694390.lock b/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/.locks/wf-dd2f5d4d4a694390.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/wf-dd2f5d4d4a694390/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/wf-dd2f5d4d4a694390/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/wf-dd2f5d4d4a694390/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/planned/workflows/wf-dd2f5d4d4a694390/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/wf-dd2f5d4d4a694390/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/wf-dd2f5d4d4a694390/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/planned/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/workflows.db new file mode 100644 index 0000000..ae4562c Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/planned/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/.locks/wf-f212e2feb55a4cb8.lock b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/.locks/wf-f212e2feb55a4cb8.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json new file mode 100644 index 0000000..dfea5ae --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-f212e2feb55a4cb8", "node_id": "draft", "lease_id": "89f3905b7269463784e77b1bdf6d8684", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "e7af9f29c5e2970e638609f7b559f96148b877510ca9e342bdfc73797d3e6e86"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json new file mode 100644 index 0000000..38a5c9b --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-f212e2feb55a4cb8", + "node_id": "draft", + "lease_id": "89f3905b7269463784e77b1bdf6d8684", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/wf-f212e2feb55a4cb8/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/workflows.db new file mode 100644 index 0000000..f06d923 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/recovery-required/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-22e223bfd8e74689.lock b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-22e223bfd8e74689.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-715ce7479b854ad2.lock b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/.locks/wf-715ce7479b854ad2.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-22e223bfd8e74689/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-22e223bfd8e74689/spec.json new file mode 100644 index 0000000..fdc8257 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-22e223bfd8e74689/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add, revised", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-715ce7479b854ad2/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-715ce7479b854ad2/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/wf-715ce7479b854ad2/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/workflows.db new file mode 100644 index 0000000..ac340c4 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/revised/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/.locks/wf-f212e2feb55a4cb8.lock b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/.locks/wf-f212e2feb55a4cb8.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json new file mode 100644 index 0000000..dfea5ae --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-f212e2feb55a4cb8", "node_id": "draft", "lease_id": "89f3905b7269463784e77b1bdf6d8684", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "e7af9f29c5e2970e638609f7b559f96148b877510ca9e342bdfc73797d3e6e86"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json new file mode 100644 index 0000000..38a5c9b --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-f212e2feb55a4cb8", + "node_id": "draft", + "lease_id": "89f3905b7269463784e77b1bdf6d8684", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/89f3905b7269463784e77b1bdf6d8684-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/wf-f212e2feb55a4cb8/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/workflows.db new file mode 100644 index 0000000..adaa112 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/running/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/manifest.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/manifest.json new file mode 100644 index 0000000..96fbd5b --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/manifest.json @@ -0,0 +1,36 @@ +{ + "schema_version": 1, + "run_id": "wf-17969765182e4464-final", + "project_id": "frozen-workflows-unapproved-completed", + "parent_run_id": null, + "created_at": "2026-11-09T10:20:38.021755+00:00", + "updated_at": "2026-11-09T10:20:38.022762+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpl1eck1pe/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "e5831b2c2512cee3b14a2bf0a0d3d8d4aeed590e", + "task_id": "ed05fece-28e2-43a8-95dc-d1571bd4cf7a", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-17969765182e4464", + "node_id": "fix", + "final_agent": "kimi-code", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "7bd7435fa52a7f95063895dcc7706b5cbb9495c226aa50545d2a4ca52a14f175", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba" + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/result.json @@ -0,0 +1,7 @@ +{ + "status": "completed", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02 +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/runs/wf-17969765182e4464-final/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/.locks/wf-17969765182e4464.lock b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/.locks/wf-17969765182e4464.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/commit.json new file mode 100644 index 0000000..ce25ece --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-17969765182e4464", "node_id": "draft", "lease_id": "23be87051d9b455a9ba667ccc82de771", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "fb83d1487ed87ad884771f399794235d54b626f20aa7fca93259b5fcc228a54d"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/result.json new file mode 100644 index 0000000..7cd9cd5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-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-17969765182e4464", + "node_id": "draft", + "lease_id": "23be87051d9b455a9ba667ccc82de771", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/23be87051d9b455a9ba667ccc82de771-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/commit.json new file mode 100644 index 0000000..b44a895 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-17969765182e4464", "node_id": "fix", "lease_id": "23be87051d9b455a9ba667ccc82de771", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "7bd7435fa52a7f95063895dcc7706b5cbb9495c226aa50545d2a4ca52a14f175"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/result.json new file mode 100644 index 0000000..185d25d --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-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-17969765182e4464", + "node_id": "fix", + "lease_id": "23be87051d9b455a9ba667ccc82de771", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/23be87051d9b455a9ba667ccc82de771-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) fix."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/wf-17969765182e4464/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/workflows.db new file mode 100644 index 0000000..7630544 Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-completed/workflows/workflows.db differ diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/manifest.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/manifest.json new file mode 100644 index 0000000..fdab079 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/manifest.json @@ -0,0 +1,36 @@ +{ + "schema_version": 1, + "run_id": "wf-2d9b5846bc1b4adf-final", + "project_id": "frozen-workflows-unapproved-running", + "parent_run_id": null, + "created_at": "2026-11-09T10:20:41.704064+00:00", + "updated_at": "2026-11-09T10:20:41.705003+00:00", + "state": "pending", + "repo_root": "/private/var/folders/4n/mt3km5rs1bn_b0w_d6hvlmhc0000gn/T/tmpl1eck1pe/repo", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "snapshot_commit": "e6e016c0b190badd6a5006c39df2a7ab1284fcaf", + "task_id": "9cb2b623-8249-4410-8ed2-b0688f868b61", + "prompt": "fix add", + "strategy": "route", + "agents": [ + "kimi-code" + ], + "plan": { + "origin": "workflow", + "workflow_id": "wf-2d9b5846bc1b4adf", + "node_id": "fix", + "final_agent": "kimi-code", + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "source_fingerprint": "8146c9e064aa8eab7389bfeebf6ecc7d9f12a5de5cada8818dcbff8a9a288bef", + "bundle_sha256": "97928fb02fee6bbc1961826ff73df1c85ced33f7e8cc227186d365c9032e4dcb", + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba" + }, + "agent_id": "kimi-code", + "files_changed": [ + "calc.py" + ], + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "provider_session_id": null, + "conversation_id": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/result.json new file mode 100644 index 0000000..b429c06 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/result.json @@ -0,0 +1,7 @@ +{ + "status": "completed", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02 +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/runs/wf-2d9b5846bc1b4adf-final/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/.locks/wf-2d9b5846bc1b4adf.lock b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/.locks/wf-2d9b5846bc1b4adf.lock new file mode 100644 index 0000000..e69de29 diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/commit.json new file mode 100644 index 0000000..10e1fe3 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-2d9b5846bc1b4adf", "node_id": "draft", "lease_id": "c500c9753a704e038f3b6be8f94f32f3", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "50088a734f61751b71487bed3dcec1ace63b48f39fc37f4146c44656f2471938"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/result.json new file mode 100644 index 0000000..5a84d9a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/result.json @@ -0,0 +1,27 @@ +{ + "status": "completed", + "agent_id": "codex", + "output": "codex (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": null, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-2d9b5846bc1b4adf", + "node_id": "draft", + "lease_id": "c500c9753a704e038f3b6be8f94f32f3", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": { + "total_input_tokens": 1, + "output_tokens": 1, + "cache_read_tokens": 0, + "cache_creation_tokens": null, + "total_cost_usd": null + } +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/stream.log new file mode 100644 index 0000000..f5748d5 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/draft/stream.log @@ -0,0 +1,3 @@ +{"type": "thread.started", "thread_id": "fake-codex-thread"} +{"type": "item.completed", "item": {"type": "agent_message", "text": "codex (fake) fix."}} +{"type": "turn.completed", "usage": {"input_tokens": 1, "output_tokens": 1}} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/commit.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/commit.json new file mode 100644 index 0000000..4ae4eda --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/commit.json @@ -0,0 +1 @@ +{"schema_version": 3, "workflow_id": "wf-2d9b5846bc1b4adf", "node_id": "fix", "lease_id": "c500c9753a704e038f3b6be8f94f32f3", "attempt": 1, "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", "bundle_sha256": "97928fb02fee6bbc1961826ff73df1c85ced33f7e8cc227186d365c9032e4dcb"} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/result.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/result.json new file mode 100644 index 0000000..b9cfe8a --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/result.json @@ -0,0 +1,33 @@ +{ + "status": "completed", + "agent_id": "kimi-code", + "output": "kimi (fake) fix.", + "error": null, + "exit_code": 0, + "duration_seconds": 0.02, + "files_changed": [ + "calc.py" + ], + "gate": { + "passed": true, + "score": 1.0, + "checks": [ + { + "command": "python3 -c \"from calc import add; assert add(2, 3) == 5\"", + "passed": true, + "exit_code": 0, + "output": "", + "duration_seconds": 0.02 + } + ] + }, + "patch_sha256": "35dc711418e60edc7974186bd853c7ab08db843f16e43066417b07a53d8566ba", + "patch_bytes": 157, + "bundle_version": 3, + "workflow_id": "wf-2d9b5846bc1b4adf", + "node_id": "fix", + "lease_id": "c500c9753a704e038f3b6be8f94f32f3", + "attempt": 1, + "spec_sha256": "977294d3defa4f3167fefdc2a3c8150025d683c5e39bb848336df5919c134283", + "usage": null +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch new file mode 100644 index 0000000..95e886e --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/c500c9753a704e038f3b6be8f94f32f3-1/winner.patch @@ -0,0 +1,8 @@ +diff --git a/calc.py b/calc.py +index 12ee743..4693ad3 100644 +--- a/calc.py ++++ b/calc.py +@@ -1,2 +1,2 @@ + def add(a, b): +- return a - b ++ return a + b diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/stream.log b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/stream.log new file mode 100644 index 0000000..9ee3cc1 --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/nodes/fix/stream.log @@ -0,0 +1,2 @@ +{"role": "assistant", "content": "kimi (fake) fix."} +{"role": "meta", "type": "session.resume_hint", "session_id": "fake-kimi-session", "content": "To resume this session: kimi -r fake-kimi-session"} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/planner-output.txt b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/planner-output.txt new file mode 100644 index 0000000..b6d2b3c --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/planner-output.txt @@ -0,0 +1 @@ +{"schema_version": 1, "goal": "fix add", "final_node": "fix", "nodes": [{"id": "draft", "agent": "codex", "prompt": "draft the fix"}, {"id": "fix", "agent": "kimi-code", "prompt": "apply the fix", "needs": ["draft"]}]} \ No newline at end of file diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/spec.json b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/spec.json new file mode 100644 index 0000000..f04e3ad --- /dev/null +++ b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/wf-2d9b5846bc1b4adf/spec.json @@ -0,0 +1,25 @@ +{ + "schema_version": 1, + "goal": "fix add", + "nodes": [ + { + "id": "draft", + "agent": "codex", + "prompt": "draft the fix", + "needs": [], + "timeout_seconds": null, + "max_attempts": 1 + }, + { + "id": "fix", + "agent": "kimi-code", + "prompt": "apply the fix", + "needs": [ + "draft" + ], + "timeout_seconds": null, + "max_attempts": 1 + } + ], + "final_node": "fix" +} diff --git a/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/workflows.db b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/workflows.db new file mode 100644 index 0000000..5baaa2f Binary files /dev/null and b/tests/fixtures/persisted/workflows/v0.3.0/unapproved-running/workflows/workflows.db differ diff --git a/tests/test_frozen_workflows.py b/tests/test_frozen_workflows.py new file mode 100644 index 0000000..81ce351 --- /dev/null +++ b/tests/test_frozen_workflows.py @@ -0,0 +1,801 @@ +"""Released workflows, read by the current readers (#271, persisted contracts D). + +The stores under ``fixtures/persisted/workflows///`` were +written by that release's own ``syncmill workflow`` commands (see ``freeze.py`` +there) and are never regenerated by the current writer. Each scenario is one +project: its workflow database, the workflow directories (spec, planner +output, node bundles, stream logs) and any workflow-origin run artifact. +Workspaces and job records were not frozen. + +Only machine-bound values are rebound, each in a named place: the repository +root and source fingerprint (recomputed in the stored value's own version) of +every workflow row, a lease owner's ``pid@host`` (set to a pid that cannot be +alive on this host), and the root and fingerprint of a final run artifact. +``spec_json``, node bundles and ``commit.json`` are never touched: digests +cover their bytes. ``_install`` asserts that every other cell is unchanged. +""" + +from __future__ import annotations + +import json +import re +import shutil +import socket +import sqlite3 +import subprocess +from pathlib import Path + +import pytest +from click.testing import CliRunner + +from syncmill.cli import cli +from syncmill.worktree import fingerprint_version, repository_fingerprint + +FROZEN = Path(__file__).parent / "fixtures" / "persisted" / "workflows" +RELEASES = sorted(p.name for p in FROZEN.iterdir() if p.is_dir()) + +#: scenario -> the workflow state each row was left in, oldest first +SCENARIOS = { + "planned": ["planned"], + "completed": ["completed"], + "no-changes": ["completed"], + "gate-failed": ["failed"], + "failed": ["failed"], + "cancelled": ["cancelled"], + "running": ["running"], + "recovery-required": ["recovery_required"], + "unapproved-completed": ["completed"], + "unapproved-running": ["running"], +} +#: v0.3.0 added plans from a document and their revisions. +SCENARIOS_V030 = {"created": ["planned"], "revised": ["planned", "planned"]} + +#: Within C ``int`` (``os.kill`` accepts it) and above any real pid_max, so +#: the liveness probe answers "no such process". +DEAD_PID = str(2**31 - 1) +GATE_COMMAND = 'python3 -c "from calc import add; assert add(2, 3) == 5"' + + +def scenarios(release: str) -> dict[str, list[str]]: + return {**SCENARIOS, **(SCENARIOS_V030 if release != "v0.2.0" else {})} + + +def _bindings(release: str) -> dict: + return json.loads((FROZEN / release / "bindings.json").read_text()) + + +def _cells(conn: sqlite3.Connection) -> dict[tuple[str, str], list]: + tables = [r[0] for r in conn.execute( + "SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%'" + )] + cells: dict[tuple[str, str], list] = {} + for table in tables: + columns = [r[1] for r in conn.execute(f"PRAGMA table_info({table})")] + rows = conn.execute(f"SELECT * FROM {table} ORDER BY rowid").fetchall() + for index, column in enumerate(columns): + cells[(table, column)] = [row[index] for row in rows] + return cells + + +#: The machine-bound columns of the workflow database. +BOUND = { + ("workflows", "repo_root"): "path", + ("workflows", "source_fingerprint"): "fingerprint", + ("workflows", "lease_owner"): "owner", +} + + +class Frozen: + """One release scenario installed as a project bound to ``repo``.""" + + def __init__(self, release: str, scenario: str, tmp_path: Path, monkeypatch): + self.release = release + self.scenario = scenario + self.bindings = _bindings(release) + self.facts = self.bindings["scenarios"][scenario] + self.dir = FROZEN / release / scenario + self.root = tmp_path.resolve() + self.repo = self.root / "repo" + self.repo.mkdir() + for name, text in self.bindings["tree"].items(): + (self.repo / name).write_text(text, encoding="utf-8") + for argv in ( + ["init", "-q"], + ["add", "-A"], + ["-c", "user.name=t", "-c", "user.email=t@t.test", "-c", + "commit.gpgsign=false", "commit", "-q", "-m", "frozen tree"], + ): + subprocess.run(["git", *argv], cwd=self.repo, check=True, capture_output=True) + self.runs_root = self.root / "state" / "runs" + self.project_dir = self.runs_root / self.facts["project_id"] + self.workflow_ids = self.facts["workflow_ids"] + self.workflow_id = self.workflow_ids[-1] + monkeypatch.setenv("SYNCMILL_RUNS__ROOT", str(self.runs_root)) + monkeypatch.setenv("SYNCMILL_WORKSPACE__ROOT", str(self.root / "state" / "workspaces")) + monkeypatch.setenv("SYNCMILL_PROJECT__ID", self.facts["project_id"]) + self.fingerprints = { + stored: repository_fingerprint(self.repo, like=stored) + for stored in self.facts["source_fingerprints"] + } + self._install() + + # ── installation and rebinding ────────────────────────────────────── + + def _rebind(self, kind: str, value): + if value is None: + return None + if kind == "path": + old = self.bindings["box_root"] + assert value == old or value.startswith(old + "/"), value + return str(self.root) + value[len(old):] + if kind == "fingerprint": + return self.fingerprints[value] + pid, _, host = value.partition("@") + assert pid.isdigit() and host == self.bindings["hostname"], value + return f"{DEAD_PID}@{socket.gethostname()}" + + def _install(self) -> None: + shutil.copytree(self.dir, self.project_dir) + db = self.project_dir / "workflows" / "workflows.db" + conn = sqlite3.connect(db) # replays a crash's WAL, as any open would + try: + before = _cells(conn) + for (table, column), kind in BOUND.items(): + rows = conn.execute(f"SELECT rowid, {column} FROM {table}").fetchall() + for rowid, value in rows: + new = self._rebind(kind, value) + if new != value: + conn.execute( + f"UPDATE {table} SET {column} = ? WHERE rowid = ?", (new, rowid) + ) + conn.commit() + after = _cells(conn) + finally: + conn.close() + assert after.keys() == before.keys() + for key in before: + if key not in BOUND: + assert after[key] == before[key], key + # A final artifact carries the fingerprint at the top level and in + # its plan, and the repository root once. Exact counts. + for manifest in self.project_dir.glob("runs/*/manifest.json"): + self._replace_bytes(manifest, fingerprints=2, paths=1) + + def _replace_bytes(self, path: Path, *, fingerprints: int, paths: int) -> None: + data = path.read_bytes() + found = 0 + for stored, live in self.fingerprints.items(): + found += data.count(stored.encode()) + data = data.replace(stored.encode(), live.encode()) + assert found == fingerprints, path + old = self.bindings["box_root"].encode() + assert data.count(old) == paths, path + data = data.replace(old, str(self.root).encode()) + path.write_bytes(data) + + # ── reading ───────────────────────────────────────────────────────── + + def rows(self, sql: str, *params) -> list[dict]: + conn = sqlite3.connect(self.project_dir / "workflows" / "workflows.db") + conn.row_factory = sqlite3.Row + try: + return [dict(row) for row in conn.execute(sql, params)] + finally: + conn.close() + + def row(self, workflow_id: str | None = None) -> dict: + [row] = self.rows( + "SELECT * FROM workflows WHERE workflow_id = ?", workflow_id or self.workflow_id + ) + return row + + def frozen_bytes(self, relative: str) -> bytes: + return (self.dir / relative).read_bytes() + + def cli(self, *argv: str): + return CliRunner().invoke(cli, [*argv, "--repo", str(self.repo)]) + + +# ── inventory ───────────────────────────────────────────────────────────── + + +def test_every_release_freezes_every_scenario(): + # A release directory that lost a scenario would silently shrink coverage. + assert RELEASES == ["v0.2.0", "v0.3.0"] + for release in RELEASES: + bindings = _bindings(release) + assert bindings["release"] == release.removeprefix("v") + assert sorted(bindings["scenarios"]) == sorted(scenarios(release)) + for name, facts in bindings["scenarios"].items(): + assert list(facts["states"].values()) == scenarios(release)[name], name + for stored in facts["source_fingerprints"]: + assert fingerprint_version(stored) == 1 + # No release wrote a v1 or v2 manifest: both normalize to v3 + # before the insert. + db = FROZEN / release / name / "workflows" / "workflows.db" + conn = sqlite3.connect(f"file:{db}?mode=ro&immutable=1", uri=True) + try: + versions = {json.loads(m)["schema_version"] + for (m,) in conn.execute("SELECT manifest_json FROM workflows")} + finally: + conn.close() + assert versions == {3}, (release, name) + + +@pytest.fixture(params=RELEASES) +def release(request) -> str: + return request.param + + +def _each(release: str) -> list[str]: + return list(scenarios(release)) + + +@pytest.mark.parametrize("scenario", list(SCENARIOS) + list(SCENARIOS_V030)) +def test_install_rebinds_only_machine_bound_values(release, scenario, tmp_path, monkeypatch): + if scenario not in scenarios(release): + pytest.skip(f"{release} has no {scenario}") + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + for row in frozen.rows("SELECT * FROM workflows"): + assert row["repo_root"] == str(frozen.repo) + assert fingerprint_version(row["source_fingerprint"]) == 1 + assert row["source_fingerprint"] == repository_fingerprint( + frozen.repo, like=row["source_fingerprint"] + ) + if row["lease_owner"] is not None: + assert row["lease_owner"] == f"{DEAD_PID}@{socket.gethostname()}" + assert re.fullmatch(r"wf-[0-9a-f]{16}", frozen.workflow_id) + + +def _immutable_rows(release: str, scenario: str) -> list[dict]: + """The rows exactly as the release left them (no open, no WAL replay).""" + db = FROZEN / release / scenario / "workflows" / "workflows.db" + conn = sqlite3.connect(f"file:{db}?mode=ro&immutable=1", uri=True) + conn.row_factory = sqlite3.Row + try: + return [dict(row) for row in conn.execute( + "SELECT * FROM workflows ORDER BY created_at, workflow_id" + )] + finally: + conn.close() + + +def _lines(text: str, prefix: str) -> list[str]: + return [line for line in text.splitlines() if line.startswith(prefix)] + + +def _clock(monkeypatch, at) -> None: + """Fix the workflow module's clock, so a lease decision cannot depend on + how old the fixture is: the released heartbeat ages in real time.""" + from datetime import datetime + + class Fixed(datetime): + @classmethod + def now(cls, tz=None): + return at if tz is None else at.astimezone(tz) + + monkeypatch.setattr("syncmill.workflow.datetime", Fixed) + + +def _fresh_heartbeat(frozen: "Frozen"): + """One second after the released heartbeat: only the owner's pid can + make the lease stale.""" + from datetime import datetime, timedelta + + return datetime.fromisoformat(frozen.row()["lease_heartbeat"]) + timedelta(seconds=1) + + +#: Scenarios whose released lease owner is dead; HEAD's show/status recover it. +DEAD_OWNER = {"running", "unapproved-running"} +#: The final state each node of the frozen spec was left in, per scenario. +NODES = { + "planned": ("pending", "pending"), + "created": ("pending", "pending"), + "revised": ("pending", "pending"), + "cancelled": ("pending", "pending"), + "completed": ("completed", "completed"), + "no-changes": ("completed", "completed"), + "unapproved-completed": ("completed", "completed"), + "gate-failed": ("completed", "failed"), + "failed": ("completed", "failed"), + "running": ("completed", "running"), + "unapproved-running": ("completed", "completed"), + "recovery-required": ("completed", "interrupted"), +} + + +# ── reading every released workflow ─────────────────────────────────────── + + +@pytest.mark.parametrize("scenario", list(SCENARIOS) + list(SCENARIOS_V030)) +def test_list_and_show_read_a_released_workflow(release, scenario, tmp_path, monkeypatch): + if scenario not in scenarios(release): + pytest.skip(f"{release} has no {scenario}") + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + released = _immutable_rows(release, scenario) + manifests = {r["workflow_id"]: json.loads(r["manifest_json"]) for r in released} + + # The release's own state, before any HEAD reader can recover it. + listed = frozen.cli("workflow", "list") + assert listed.exit_code == 0, listed.output + shown_rows = [line.split()[:2] for line in listed.output.splitlines() + if line.startswith("wf-")] + assert sorted(shown_rows) == sorted( + [r["workflow_id"], r["state"]] for r in released + ) + for row in released: + manifest = manifests[row["workflow_id"]] + assert [manifest["nodes"][n]["state"] for n in ("draft", "fix")] == list(NODES[scenario]) + + shown = frozen.cli("workflow", "show", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + text = shown.output + row = released[-1] + manifest = manifests[row["workflow_id"]] + expected_state = "recovery_required" if scenario in DEAD_OWNER else row["state"] + origin = {"created": "file", "revised": "revision"}.get(scenario, "planner") + assert _lines(text, "workflow: ") == [f"workflow: {frozen.workflow_id}"] + assert _lines(text, "state: ") == [f"state: {expected_state}"] + assert _lines(text, "calls_used: ") == [f"calls_used: {manifest['calls_used']}"] + usage = manifest["usage"] + assert _lines(text, "usage: ") == [ + f"usage: input={usage['input_tokens']} output={usage['output_tokens']} " + f"cost_usd={usage['total_cost_usd']:.6f}" + ] + loaded = _store(frozen).load(frozen.workflow_id) + assert (loaded.input_tokens, loaded.output_tokens, loaded.total_cost_usd) == ( + usage["input_tokens"], usage["output_tokens"], usage["total_cost_usd"] + ) + if scenario not in ("created", "revised"): + # The planner (and any node that ran) reported tokens: zero would + # not tell a dropped value from a recorded one. + assert usage["input_tokens"] > 0 + assert _lines(text, "plan_origin: ") == [f"plan_origin: {origin}"] + assert _lines(text, "goal: ") == [f"goal: {json.loads(row['spec_json'])['goal']}"] + final = row["final_run_id"] + assert _lines(text, "final_run: ") == ([f"final_run: {final}"] if final else []) + states = dict(zip(("draft", "fix"), NODES[scenario])) + if scenario in DEAD_OWNER: + # The owner is gone: the node it was running is interrupted. + states = {n: ("interrupted" if s == "running" else s) for n, s in states.items()} + assert _lines(text, " draft: ") == [ + f" draft: {states['draft']} agent=codex " + f"attempts={manifest['nodes']['draft']['attempts']}/1 needs=-" + ] + assert _lines(text, " fix: ") == [ + f" fix: {states['fix']} agent=kimi-code " + f"attempts={manifest['nodes']['fix']['attempts']}/1 needs=draft (final)" + ] + + +@pytest.mark.parametrize("scenario", sorted(DEAD_OWNER)) +@pytest.mark.parametrize("command", ["show", "status"]) +def test_show_and_status_recover_a_released_dead_owner( + release, scenario, command, tmp_path, monkeypatch +): + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + _clock(monkeypatch, _fresh_heartbeat(frozen)) + before = frozen.row() + assert before["state"] == "running" + assert before["lease_owner"] == f"{DEAD_PID}@{socket.gethostname()}" + result = frozen.cli("workflow", command, frozen.workflow_id) + assert result.exit_code == 0, result.output + after = frozen.row() + assert (after["state"], after["lease_owner"], after["lease_id"]) == ( + "recovery_required", None, None + ) + manifest = json.loads(after["manifest_json"]) + assert manifest["error"] == "execution owner lost; resume explicitly" + # The node the dead owner was running is interrupted; its finished + # dependency keeps its bundle. + released = json.loads(before["manifest_json"])["nodes"] + assert manifest["nodes"]["draft"] == released["draft"] + expected_fix = "interrupted" if released["fix"]["state"] == "running" else released["fix"]["state"] + assert manifest["nodes"]["fix"]["state"] == expected_fix + + +@pytest.mark.parametrize("command", ["show", "status"]) +def test_a_released_lease_whose_owner_lives_is_never_taken(release, command, tmp_path, monkeypatch): + # The released owner is read as pid@host: on this host the pid's answer + # is final, so a live owner keeps the lease although the released + # heartbeat is long past the lease timeout. + import os + + frozen = Frozen(release, "running", tmp_path, monkeypatch) + conn = sqlite3.connect(frozen.project_dir / "workflows" / "workflows.db") + try: + conn.execute("UPDATE workflows SET lease_owner = ?", + (f"{os.getpid()}@{socket.gethostname()}",)) + conn.commit() + finally: + conn.close() + # Long past any lease timeout: only the pid's answer keeps the lease. + from datetime import UTC, datetime + + _clock(monkeypatch, datetime(2100, 1, 1, tzinfo=UTC)) + lease = ("state", "lease_owner", "lease_id", "lease_heartbeat", "manifest_json", "revision") + before = {key: frozen.row()[key] for key in lease} + result = frozen.cli("workflow", command, frozen.workflow_id) + assert result.exit_code == 0, result.output + # (Opening a v0.2.0 store adds v0.3.0's columns; the lease is untouched.) + assert {key: frozen.row()[key] for key in lease} == before + assert before["state"] == "running" + + +def test_a_release_recovered_workflow_reads_as_written(release, tmp_path, monkeypatch): + frozen = Frozen(release, "recovery-required", tmp_path, monkeypatch) + [released] = _immutable_rows(release, "recovery-required") + shown = frozen.cli("workflow", "status", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + # Nothing to recover: the row is exactly as the release recovered it. + after = frozen.row() + assert {k: after[k] for k in ("state", "manifest_json", "revision", "lease_owner")} == { + k: released[k] for k in ("state", "manifest_json", "revision", "lease_owner") + } + # The release recovered it from the job record the killed run left (no + # job records are frozen, so HEAD's own recovery above words it as a lost + # owner instead). + assert json.loads(after["manifest_json"])["error"] == ( + "durable workflow job ended in failed; resume explicitly" + ) + + +@pytest.mark.parametrize("scenario", ["planned", "completed", "created", "revised"]) +def test_the_stored_spec_reads_back_with_its_digest(release, scenario, tmp_path, monkeypatch): + if scenario not in scenarios(release): + pytest.skip(f"{release} has no {scenario}") + from syncmill.workflow import WorkflowSpec + + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + for released in _immutable_rows(release, scenario): + workflow_id = released["workflow_id"] + manifest = json.loads(released["manifest_json"]) + spec = WorkflowSpec.model_validate_json(released["spec_json"]) + assert spec.digest() == manifest["spec_sha256"] + # The spec file beside the row holds the same plan. + on_disk = json.loads(frozen.frozen_bytes(f"workflows/{workflow_id}/spec.json")) + assert WorkflowSpec.model_validate(on_disk).digest() == manifest["spec_sha256"] + + prompts = frozen.cli("workflow", "show", workflow_id, "--prompts") + assert prompts.exit_code == 0, prompts.output + lines = prompts.output.splitlines() + for node in spec.nodes: + at = lines.index(" prompt:", lines.index(next( + line for line in lines if line.startswith(f" {node.id}: ") + ))) + assert lines[at + 1] == f" {node.prompt}" + + exported = frozen.cli("workflow", "export", workflow_id) + assert exported.exit_code == 0, exported.output + document = json.loads(exported.stdout) + assert WorkflowSpec.model_validate(document).digest() == manifest["spec_sha256"] + assert document["goal"] == spec.goal + assert [(n["id"], n["agent"], n["prompt"], n["needs"]) for n in document["nodes"]] == [ + (n.id, n.agent, n.prompt, n.needs) for n in spec.nodes + ] + # spec_json itself is never rebound or rewritten. + assert [r["spec_json"] for r in frozen.rows("SELECT spec_json FROM workflows " + "ORDER BY created_at, workflow_id")] == [ + r["spec_json"] for r in _immutable_rows(release, scenario) + ] + + +@pytest.mark.parametrize("scenario", ["completed", "revised"]) +def test_lineage_reads_a_released_workflow(release, scenario, tmp_path, monkeypatch): + if scenario not in scenarios(release): + pytest.skip(f"{release} has no {scenario}") + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + released = _immutable_rows(release, scenario) + shown = frozen.cli("workflow", "lineage", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + expected = [] + for version, row in enumerate(released, 1): + digest = json.loads(row["manifest_json"])["spec_sha256"] + origin = "planner" if scenario == "completed" else ("file" if version == 1 else "revision") + parent = "-" if version == 1 else released[0]["workflow_id"] + marker = "*" if row["workflow_id"] == frozen.workflow_id else " " + expected.append((marker, f"v{version}", row["workflow_id"], origin, row["state"], + f"parent={parent}", f"digest={digest}")) + assert [tuple(line.split()) if line.startswith("*") else (" ", *line.split()) + for line in shown.output.splitlines() if line.strip()] == expected + + +def _store(frozen: Frozen): + from syncmill.workflow import WorkflowStore + + return WorkflowStore(frozen.runs_root, frozen.facts["project_id"]) + + +def test_events_read_as_the_release_wrote_them(release, tmp_path, monkeypatch): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + db = FROZEN / release / "completed" / "workflows" / "workflows.db" + conn = sqlite3.connect(f"file:{db}?mode=ro&immutable=1", uri=True) + try: + written = conn.execute( + "SELECT seq, event_type, node_id, payload_json FROM workflow_events " + "WHERE workflow_id = ? ORDER BY seq", (frozen.workflow_id,) + ).fetchall() + finally: + conn.close() + assert len(written) >= 2 + events = _store(frozen).events(frozen.workflow_id) + assert [(e["seq"], e["event_type"], e["node_id"], e["payload"]) for e in events] == [ + (seq, kind, node, json.loads(payload)) for seq, kind, node, payload in written + ] + middle = written[len(written) // 2][0] + assert [e["seq"] for e in _store(frozen).events(frozen.workflow_id, after=middle)] == [ + seq for seq, *_ in written if seq > middle + ] + + +@pytest.mark.parametrize("scenario", ["completed", "no-changes", "gate-failed", "running"]) +def test_released_node_bundles_verify(release, scenario, tmp_path, monkeypatch): + import hashlib + + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + nodes = json.loads(frozen.row()["manifest_json"])["nodes"] + store = _store(frozen) + verified = [] + for node_id, node in nodes.items(): + if node["bundle_sha256"] is None: + continue + record, result, patch = store.read_node_bundle( + frozen.workflow_id, node_id, node["output_path"], node["patch_path"] + ) + base = f"workflows/{frozen.workflow_id}" + assert result == frozen.frozen_bytes(f"{base}/{node['output_path']}") + assert patch == frozen.frozen_bytes(f"{base}/{node['patch_path']}") + commit = json.loads(frozen.frozen_bytes( + f"{base}/{Path(node['output_path']).parent}/commit.json" + )) + assert commit["schema_version"] == 3 + assert commit["bundle_sha256"] == node["bundle_sha256"] == hashlib.sha256( + result + b"\0" + patch + ).hexdigest() + assert (record["node_id"], record["workflow_id"]) == (node_id, frozen.workflow_id) + verified.append(node_id) + # Every finished node left a bundle: both for a finished run, only the + # dependency for a run that died or failed in the final node. + assert verified == {"running": ["draft"]}.get(scenario, ["draft", "fix"]) + + +def _corrupt_patch(bundle: Path) -> None: + patch = bundle / "winner.patch" + patch.write_bytes(patch.read_bytes() + b"\n") + + +def _corrupt_result(bundle: Path) -> None: + result = bundle / "result.json" + result.write_bytes(result.read_bytes().replace(b'"exit_code": 0', b'"exit_code": 1', 1)) + + +def _corrupt_commit_version(bundle: Path) -> None: + commit = bundle / "commit.json" + record = json.loads(commit.read_text()) + commit.write_text(json.dumps({**record, "schema_version": 2})) + + +def _corrupt_commit_digest(bundle: Path) -> None: + commit = bundle / "commit.json" + record = json.loads(commit.read_text()) + commit.write_text(json.dumps({**record, "bundle_sha256": "0" * 64})) + + +@pytest.mark.parametrize( + "damage", [_corrupt_patch, _corrupt_result, _corrupt_commit_version, _corrupt_commit_digest] +) +def test_a_damaged_released_bundle_is_refused(release, damage, tmp_path, monkeypatch): + from syncmill.workflow import WorkflowError + + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + node = json.loads(frozen.row()["manifest_json"])["nodes"]["draft"] + bundle = frozen.project_dir / "workflows" / frozen.workflow_id / Path(node["output_path"]).parent + before = (bundle / "result.json").read_bytes() + damage(bundle) + assert (bundle / "result.json").read_bytes() != before or damage is not _corrupt_result + with pytest.raises(WorkflowError, match="missing or corrupt bundle"): + _store(frozen).read_node_bundle( + frozen.workflow_id, "draft", node["output_path"], node["patch_path"] + ) + + +def test_a_released_patch_that_disagrees_with_its_result_is_refused( + release, tmp_path, monkeypatch +): + # The patch is altered and the commit record and the database re-sealed + # around it, so only result.json's own patch digest still disagrees. + import hashlib + + from syncmill.workflow import WorkflowError + + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + row = frozen.row() + manifest = json.loads(row["manifest_json"]) + node = manifest["nodes"]["draft"] + bundle = frozen.project_dir / "workflows" / frozen.workflow_id / Path(node["output_path"]).parent + patch = (bundle / "winner.patch").read_bytes() + b"\n" + (bundle / "winner.patch").write_bytes(patch) + sealed = hashlib.sha256((bundle / "result.json").read_bytes() + b"\0" + patch).hexdigest() + commit = json.loads((bundle / "commit.json").read_text()) + (bundle / "commit.json").write_text(json.dumps({**commit, "bundle_sha256": sealed})) + node["bundle_sha256"] = sealed + conn = sqlite3.connect(frozen.project_dir / "workflows" / "workflows.db") + try: + conn.execute("UPDATE workflows SET manifest_json = ? WHERE workflow_id = ?", + (json.dumps(manifest), frozen.workflow_id)) + conn.commit() + finally: + conn.close() + with pytest.raises(WorkflowError, match="missing or corrupt bundle"): + _store(frozen).read_node_bundle( + frozen.workflow_id, "draft", node["output_path"], node["patch_path"] + ) + + +def test_a_released_bundle_the_database_no_longer_vouches_for_is_refused( + release, tmp_path, monkeypatch +): + # The bundle files are intact; only the database's record of the bundle + # digest changed. + from syncmill.workflow import WorkflowError + + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + manifest = json.loads(frozen.row()["manifest_json"]) + node = manifest["nodes"]["draft"] + node["bundle_sha256"] = "0" * 64 + conn = sqlite3.connect(frozen.project_dir / "workflows" / "workflows.db") + try: + conn.execute("UPDATE workflows SET manifest_json = ? WHERE workflow_id = ?", + (json.dumps(manifest), frozen.workflow_id)) + conn.commit() + finally: + conn.close() + with pytest.raises(WorkflowError, match="missing or corrupt bundle"): + _store(frozen).read_node_bundle( + frozen.workflow_id, "draft", node["output_path"], node["patch_path"] + ) + + +def test_logs_show_the_released_stream_logs(release, tmp_path, monkeypatch): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + shown = frozen.cli("workflow", "logs", frozen.workflow_id) + assert shown.exit_code == 0, shown.output + expected = [] + for node in ("draft", "fix"): + log = frozen.frozen_bytes(f"workflows/{frozen.workflow_id}/nodes/{node}/stream.log") + expected += [f"== {node} ==", *log.decode().splitlines()] + assert [line for line in shown.stdout.splitlines() if line] == [line for line in expected if line] + + +def test_cancel_a_released_planned_workflow(release, tmp_path, monkeypatch): + frozen = Frozen(release, "planned", tmp_path, monkeypatch) + cancelled = frozen.cli("workflow", "cancel", frozen.workflow_id) + assert cancelled.exit_code == 0, cancelled.output + assert frozen.row()["state"] == "cancelled" + + +class _Later: + """A clock long after every fixture was written.""" + + @staticmethod + def install(monkeypatch): + from datetime import UTC, datetime + + class Later(datetime): + @classmethod + def now(cls, tz=None): + return datetime(2100, 1, 1, tzinfo=tz or UTC) + + monkeypatch.setattr("syncmill.workflow.datetime", Later) + + +@pytest.mark.parametrize( + ("scenario", "pruned"), + [("completed", True), ("failed", True), ("cancelled", True), ("planned", False), + ("recovery-required", False)], +) +def test_prune_removes_only_released_terminal_workflows( + release, scenario, pruned, tmp_path, monkeypatch +): + frozen = Frozen(release, scenario, tmp_path, monkeypatch) + _Later.install(monkeypatch) + result = frozen.cli("workflow", "prune", "--keep", "0", "--retention-days", "1") + assert result.exit_code == 0, result.output + remaining = [r["workflow_id"] for r in frozen.rows("SELECT workflow_id FROM workflows")] + directory = frozen.project_dir / "workflows" / frozen.workflow_id + if pruned: + assert remaining == [] and not directory.exists() + else: + assert remaining == [frozen.workflow_id] and (directory / "spec.json").is_file() + + +# ── the workflow-origin run artifact ────────────────────────────────────── + + +def _final(frozen: Frozen) -> tuple[str, Path]: + [run_id] = frozen.facts["run_artifacts"] + return run_id, frozen.project_dir / "runs" / run_id + + +def test_diff_and_show_read_a_released_final_artifact(release, tmp_path, monkeypatch): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + assert run_id == frozen.row()["final_run_id"] == f"{frozen.workflow_id}-final" + diff = frozen.cli("diff", run_id) + assert diff.exit_code == 0, diff.output + assert diff.stdout == (run_dir / "winner.patch").read_text() + shown = frozen.cli("runs", "show", run_id) + assert shown.exit_code == 0, shown.output + manifest = json.loads((run_dir / "manifest.json").read_text()) + assert json.loads(shown.stdout) == manifest + plan = manifest["plan"] + assert (plan["origin"], plan["workflow_id"], plan["node_id"]) == ( + "workflow", frozen.workflow_id, "fix" + ) + # v0.3.0 records the final node's agent; v0.2.0 did not. + assert plan.get("final_agent") == (None if release == "v0.2.0" else "kimi-code") + + +@pytest.mark.parametrize("prune_first", [False, True]) +def test_apply_a_released_approved_final_artifact( + release, prune_first, tmp_path, monkeypatch +): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + run_id, _ = _final(frozen) + if prune_first: + # Its approval is on the artifact: losing the workflow row is fine. + _Later.install(monkeypatch) + assert frozen.cli("workflow", "prune", "--keep", "0", "--retention-days", "1").exit_code == 0 + assert frozen.rows("SELECT * FROM workflows") == [] + applied = frozen.cli("apply", run_id) + assert applied.exit_code == 0, applied.output + assert (frozen.repo / "calc.py").read_text() == "def add(a, b):\n return a + b\n" + + +def test_apply_heals_a_released_approval_the_crash_never_wrote(release, tmp_path, monkeypatch): + frozen = Frozen(release, "unapproved-completed", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + assert "approved" not in json.loads((run_dir / "manifest.json").read_text())["plan"] + applied = frozen.cli("apply", run_id) + assert applied.exit_code == 0, applied.output + assert (frozen.repo / "calc.py").read_text() == "def add(a, b):\n return a + b\n" + assert "approved" in json.loads((run_dir / "manifest.json").read_text())["plan"] + + +def test_apply_refuses_a_released_final_artifact_its_workflow_never_completed( + release, tmp_path, monkeypatch +): + frozen = Frozen(release, "unapproved-running", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + before = (run_dir / "manifest.json").read_bytes() + refused = frozen.cli("apply", run_id) + assert refused.exit_code == 1 + assert refused.output.splitlines()[-1] == ( + f"Error: run {run_id} came from workflow {frozen.workflow_id}, which is " + "'running', not completed. Its result is not approved for apply." + ) + assert (run_dir / "manifest.json").read_bytes() == before + assert (frozen.repo / "calc.py").read_text() == frozen.bindings["tree"]["calc.py"] + + +def test_apply_of_a_released_no_changes_artifact(release, tmp_path, monkeypatch): + frozen = Frozen(release, "no-changes", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + assert json.loads((run_dir / "manifest.json").read_text())["state"] == "no_changes" + assert (run_dir / "winner.patch").read_bytes() == b"" + result = frozen.cli("apply", run_id) + assert (result.exit_code, result.output.splitlines()[-1]) == ( + 1, f"Error: run {run_id} is 'no_changes', not pending" + ) + + +def test_recover_skips_a_released_final_artifact(release, tmp_path, monkeypatch): + frozen = Frozen(release, "completed", tmp_path, monkeypatch) + run_id, run_dir = _final(frozen) + before = (run_dir / "manifest.json").read_bytes() + recovered = frozen.cli("recover", run_id) + assert recovered.exit_code == 0, recovered.output + assert recovered.output.splitlines() == [ + f"Run {run_id}: skipped \N{EM DASH} run is 'pending', not applying" + ] + assert (run_dir / "manifest.json").read_bytes() == before