import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { createHash } from "node:crypto";
import { afterEach, describe, expect, it } from "vitest";
import { createRandomId } from "@best-agent/foundation";
import type { ItemId, NodeContract, PlanProjection, VerdictRecord } from "@best-agent/protocol";
import { PLAN_UPDATE_TOOL_NAME } from "../provider/provider-tool-names.js";
import { LocalThreadStore, REPLAY_TOOL_INPUT_ASSET_CONTENT_TYPE } from "./thread-store.js";

const storageRoots: string[] = [];

afterEach(() => {
  while (storageRoots.length > 0) {
    rmSync(storageRoots.pop() as string, { force: true, recursive: true });
  }
});

describe("local thread store", () => {
  it("ignores invalid persisted provider tool names on tool-call items", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-provider-tool-name-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "provider tool name" });
    const turn = await store.createTurn({ threadId: thread.id });
    const itemId = createRandomId<"ItemId">("item");
    await store.appendItems([
      {
        id: itemId,
        threadId: thread.id,
        turnId: turn.id,
        callId: "item_call" as ItemId,
        createdAt: new Date().toISOString(),
        input: "query",
        kind: "tool-call",
        providerToolName: "mcp_docs_search_abcd1234",
        toolName: "mcp:docs:search",
      },
    ]);
    store.close();

    const database = new DatabaseSync(join(storageRoot, "state.sqlite"));
    const row = database.prepare("SELECT data FROM items WHERE id = ?").get(itemId) as {
      data: string;
    };
    database
      .prepare("UPDATE items SET data = ? WHERE id = ?")
      .run(
        JSON.stringify({ ...JSON.parse(row.data), providerToolName: "mcp:docs:search" }),
        itemId,
      );
    database.close();

    const reopened = new LocalThreadStore({ storageRoot });
    const items = await reopened.listItems(thread.id);
    reopened.close();

    expect(items).toHaveLength(1);
    expect(items[0]).toMatchObject({
      kind: "tool-call",
      toolName: "mcp:docs:search",
    });
    if (items[0]?.kind === "tool-call") {
      expect(items[0].providerToolName).toBeUndefined();
    }
  });

  it("ignores reserved persisted provider tool names on tool-call items", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-reserved-tool-name-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "reserved tool name" });
    const turn = await store.createTurn({ threadId: thread.id });
    await store.appendItems([
      {
        id: createRandomId<"ItemId">("item"),
        threadId: thread.id,
        turnId: turn.id,
        callId: "item_call" as ItemId,
        createdAt: new Date().toISOString(),
        input: "{}",
        kind: "tool-call",
        providerToolName: PLAN_UPDATE_TOOL_NAME,
        toolName: "plugin:plan_update",
      },
    ]);

    const items = await store.listItems(thread.id);
    store.close();

    expect(items).toHaveLength(1);
    if (items[0]?.kind === "tool-call") {
      expect(items[0].providerToolName).toBeUndefined();
    }
  });

  it("drops invalid stored tool-call history input during hydrate", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-tool-input-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "tool input" });
    const turn = await store.createTurn({ threadId: thread.id });
    const itemId = createRandomId<"ItemId">("item");
    await store.appendItems([
      {
        id: itemId,
        threadId: thread.id,
        turnId: turn.id,
        callId: "item_call" as ItemId,
        createdAt: new Date().toISOString(),
        input: "query",
        kind: "tool-call",
        historyInput: { query: "safe" },
        toolName: "workspace-search",
      },
    ]);
    store.close();

    const database = new DatabaseSync(join(storageRoot, "state.sqlite"));
    const row = database.prepare("SELECT data FROM items WHERE id = ?").get(itemId) as {
      data: string;
    };
    database
      .prepare("UPDATE items SET data = ? WHERE id = ?")
      .run(JSON.stringify({ ...JSON.parse(row.data), historyInput: "{bad json" }), itemId);
    database.close();

    const reopened = new LocalThreadStore({ storageRoot });
    const items = await reopened.listItems(thread.id);
    reopened.close();

    expect(items).toHaveLength(1);
    expect(items[0]).toMatchObject({
      kind: "tool-call",
      toolName: "workspace-search",
    });
    if (items[0]?.kind === "tool-call") {
      expect(items[0].historyInput).toBeUndefined();
    }
  });

  it("sanitizes legacy stored content tool input during hydrate", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-legacy-content-input-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "legacy content input" });
    const turn = await store.createTurn({ threadId: thread.id });
    const itemId = createRandomId<"ItemId">("item");
    const content = "legacy secret\n".repeat(1_000);
    await store.appendItems([
      {
        id: itemId,
        threadId: thread.id,
        turnId: turn.id,
        callId: "item_call" as ItemId,
        createdAt: new Date().toISOString(),
        input: "out.txt",
        kind: "tool-call",
        toolName: "write-workspace",
      },
    ]);
    store.close();

    const database = new DatabaseSync(join(storageRoot, "state.sqlite"));
    const row = database.prepare("SELECT data FROM items WHERE id = ?").get(itemId) as {
      data: string;
    };
    database.prepare("UPDATE items SET data = ? WHERE id = ?").run(
      JSON.stringify({
        ...JSON.parse(row.data),
        normalizedInput: JSON.stringify({ content, path: "out.txt" }),
      }),
      itemId,
    );
    database.close();

    const reopened = new LocalThreadStore({ storageRoot });
    const items = await reopened.listItems(thread.id);
    reopened.close();

    expect(items).toHaveLength(1);
    if (items[0]?.kind === "tool-call") {
      expect(items[0].historyInput).toEqual({
        content_bytes: Buffer.byteLength(content, "utf8"),
        path: "out.txt",
      });
      expect(JSON.stringify(items[0].historyInput)).not.toContain(content);
    }
  });

  it("migrates legacy thread databases before creating lineage indexes", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-legacy-`);
    storageRoots.push(storageRoot);

    const database = new DatabaseSync(join(storageRoot, "state.sqlite"));
    database.exec(`
      CREATE TABLE threads (
        id TEXT PRIMARY KEY,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
        cwd TEXT NOT NULL,
        title TEXT NOT NULL,
        status TEXT NOT NULL,
        archived_at TEXT
      );

      CREATE TABLE turns (
        id TEXT PRIMARY KEY,
        thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
        status TEXT NOT NULL,
        created_at TEXT NOT NULL,
        completed_at TEXT
      );

      CREATE TABLE items (
        sequence INTEGER PRIMARY KEY AUTOINCREMENT,
        id TEXT NOT NULL UNIQUE,
        thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
        turn_id TEXT NOT NULL REFERENCES turns(id) ON DELETE CASCADE,
        kind TEXT NOT NULL,
        created_at TEXT NOT NULL,
        data TEXT NOT NULL
      );

      CREATE TABLE assets (
        id TEXT PRIMARY KEY,
        thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
        turn_id TEXT REFERENCES turns(id) ON DELETE SET NULL,
        kind TEXT NOT NULL,
        storage_class TEXT NOT NULL,
        name TEXT NOT NULL,
        content_type TEXT,
        size_bytes INTEGER NOT NULL,
        blob_key TEXT,
        relative_path TEXT,
        created_at TEXT NOT NULL
      );
    `);
    database.close();

    const store = new LocalThreadStore({ storageRoot });
    await expect(store.listThreads()).resolves.toEqual([]);
    const thread = await store.createThread({ cwd: storageRoot, title: "migrated thread" });
    expect(thread.mode).toBe("default");
    store.close();
  });

  it("hydrates legacy hidden runtime reasoning summaries as draft", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-legacy-tool-reasoning-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "legacy tool reasoning" });
    const turn = await store.createTurn({ threadId: thread.id });

    await store.appendItems([
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        kind: "reasoning",
        summary: "The explore profile selected tool read-file: Read README.md before replying.",
      },
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        kind: "reasoning",
        summary:
          "Workflow phase explore started under the explore profile and is running in thread thread_legacy.",
      },
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        kind: "reasoning",
        summary: "Workflow phase verify result: Confirmed the patch fixes the issue.",
      },
    ]);

    const items = await store.listItems(thread.id);
    expect(items[0]).toMatchObject({
      kind: "reasoning",
      status: "draft",
      summary: "The explore profile selected tool read-file: Read README.md before replying.",
    });
    expect(items[1]).toMatchObject({
      kind: "reasoning",
      status: "draft",
      summary:
        "Workflow phase explore started under the explore profile and is running in thread thread_legacy.",
    });
    expect(items[2]).toMatchObject({
      kind: "reasoning",
      status: "draft",
      summary: "Workflow phase verify result: Confirmed the patch fixes the issue.",
    });
  });

  it("persists user message content parts alongside transcript text", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-prompt-content-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "prompt content" });
    const turn = await store.createTurn({ threadId: thread.id });

    await store.appendItems([
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        kind: "message",
        role: "user",
        text: "[diagram.png](/tmp/diagram.png)\nWhat is in this image?",
        contentParts: [
          {
            type: "image",
            filename: "diagram.png",
            mediaType: "image/png",
            path: "/tmp/diagram.png",
          },
          {
            type: "text",
            text: "\nWhat is in this image?",
          },
        ],
      },
    ]);

    const items = await store.listItems(thread.id);
    expect(items).toContainEqual(
      expect.objectContaining({
        kind: "message",
        role: "user",
        text: "[diagram.png](/tmp/diagram.png)\nWhat is in this image?",
        contentParts: [
          {
            type: "image",
            filename: "diagram.png",
            mediaType: "image/png",
            path: "/tmp/diagram.png",
          },
          {
            type: "text",
            text: "\nWhat is in this image?",
          },
        ],
      }),
    );
  });

  it("persists tool result structured content alongside output text", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-tool-result-structured-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({
      cwd: storageRoot,
      title: "tool result structured content",
    });
    const turn = await store.createTurn({ threadId: thread.id });

    await store.appendItems([
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        callId: "call_tool_1" as never,
        kind: "tool-result",
        outcome: "success",
        toolName: "ask-user",
        output: '{"meal":"Hotpot"}',
        structuredContent: {
          answers: {
            meal: "Hotpot",
          },
        },
      },
    ]);

    const items = await store.listItems(thread.id);
    expect(items).toContainEqual(
      expect.objectContaining({
        kind: "tool-result",
        outcome: "success",
        toolName: "ask-user",
        output: '{"meal":"Hotpot"}',
        structuredContent: {
          answers: {
            meal: "Hotpot",
          },
        },
      }),
    );
  });

  it("persists blocked turns and structured failure metadata", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-failure-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "failure metadata" });
    const turn = await store.createTurn({ threadId: thread.id });

    await store.appendItems([
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        callId: "call_tool_1" as never,
        kind: "tool-call",
        toolName: "shell",
        input: "uname",
      },
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        callId: "call_tool_1" as never,
        kind: "tool-result",
        outcome: "success",
        toolName: "shell",
        output: "preview output",
        traceAssetId: "asset_trace_1" as never,
      },
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        callId: "call_tool_1" as never,
        errorCode: "APPROVAL_REJECTED",
        kind: "error",
        message: "Approval rejected for command uname.",
        recoverability: "blocked",
        scope: "approval",
        synthetic: true,
      },
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: turn.id,
        createdAt: new Date().toISOString(),
        action: "interrupted",
        callId: "call_tool_1" as never,
        kind: "control",
        recoverability: "interrupted",
        scope: "tool",
        summary: "Tool interrupted during reconciliation.",
        synthetic: true,
      },
    ]);
    const blocked = await store.updateTurnStatus(turn.id, "blocked");

    expect(blocked.status).toBe("blocked");

    const history = await store.readHistory({ threadId: thread.id });
    expect(history.items).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          callId: "call_tool_1",
          kind: "tool-call",
        }),
        expect.objectContaining({
          callId: "call_tool_1",
          kind: "tool-result",
          outcome: "success",
          traceAssetId: "asset_trace_1",
        }),
        expect.objectContaining({
          errorCode: "APPROVAL_REJECTED",
          kind: "error",
          recoverability: "blocked",
          scope: "approval",
          synthetic: true,
        }),
        expect.objectContaining({
          action: "interrupted",
          kind: "control",
          recoverability: "interrupted",
          scope: "tool",
          synthetic: true,
        }),
      ]),
    );
  });

  it("persists runs and attempts alongside turns", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-runs-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "run metadata" });
    const turn = await store.createTurn({ threadId: thread.id });
    const run = await store.createRun({
      graphId: `${thread.id}:ordinary-turn`,
      graphRevision: 1,
      nodeId: "turn-root",
      phaseIndex: 0,
      phaseName: "summarize",
      threadId: thread.id,
      turnId: turn.id,
    });
    const attempt = await store.createRunAttempt({
      ownerId: "vitest-runtime",
      ownerHostId: "host-vitest",
      runId: run.id,
    });

    expect(await store.getRunByTurn(turn.id)).toMatchObject({
      graphId: `${thread.id}:ordinary-turn`,
      graphRevision: 1,
      id: run.id,
      nodeId: "turn-root",
      phaseName: "summarize",
      phaseStartedAt: run.startedAt,
      status: "running",
      turnId: turn.id,
    });
    expect(await store.listRunAttempts(run.id)).toEqual([
      expect.objectContaining({
        id: attempt.id,
        leaseEpoch: 1,
        ownerId: "vitest-runtime",
        ownerHostId: "host-vitest",
        restartCount: 0,
        stallCount: 0,
        status: "running",
        takeoverCount: 0,
      }),
    ]);

    const checkpoint = await store.saveRunCheckpoint({
      attemptId: attempt.id,
      graphId: `${thread.id}:ordinary-turn`,
      graphRevision: 1,
      kind: "response-output",
      nodeId: "turn-root",
      payload: { kind: "response-output" },
      runId: run.id,
      turnId: turn.id,
    });
    expect(await store.getPendingRunCheckpointByTurn(turn.id)).toMatchObject({
      graphId: `${thread.id}:ordinary-turn`,
      graphRevision: 1,
      id: checkpoint.id,
      nodeId: "turn-root",
    });

    const settledRun = await store.updateRun(run.id, { status: "completed" });
    const settledAttempt = await store.updateRunAttempt(attempt.id, { status: "completed" });

    expect(settledRun).toMatchObject({
      id: run.id,
      status: "completed",
    });
    expect(settledAttempt).toMatchObject({
      id: attempt.id,
      status: "completed",
    });
    expect(await store.listRuns(thread.id)).toEqual([
      expect.objectContaining({
        id: run.id,
        phaseStartedAt: run.startedAt,
        status: "completed",
      }),
    ]);
  });

  it("persists queued turn actor metadata", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-queued-actor-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "queued actor" });

    await store.enqueueTurn({
      actor: {
        displayName: "Slack User",
        id: "U123",
        source: "slack",
      },
      prompt: "queued follow-up",
      threadId: thread.id,
    });

    expect(await store.listQueuedTurns(thread.id)).toEqual([
      expect.objectContaining({
        actor: {
          displayName: "Slack User",
          id: "U123",
          source: "slack",
        },
        prompt: "queued follow-up",
      }),
    ]);
  });

  it("persists queued turn prompt content", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-queued-prompt-content-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "queued prompt content" });
    const promptContent = [
      { type: "text", text: "Describe this image." },
      { filename: "diagram.png", mediaType: "image/png", path: "/tmp/diagram.png", type: "image" },
    ] as const;

    await store.enqueueTurn({
      prompt: "Describe this image.",
      promptContent: [...promptContent],
      threadId: thread.id,
    });

    expect(await store.listQueuedTurns(thread.id)).toEqual([
      expect.objectContaining({
        prompt: "Describe this image.",
        promptContent: [...promptContent],
      }),
    ]);
  });

  it("backfills phaseStartedAt for legacy run rows", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-run-phase-started-at-`);
    storageRoots.push(storageRoot);

    const database = new DatabaseSync(join(storageRoot, "state.sqlite"));
    database.exec(`
      CREATE TABLE threads (
        id TEXT PRIMARY KEY,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
        cwd TEXT NOT NULL,
        mode TEXT NOT NULL DEFAULT 'default',
        metadata TEXT NOT NULL DEFAULT '{}',
        title TEXT NOT NULL,
        status TEXT NOT NULL,
        archived_at TEXT,
        forked_from_thread_id TEXT,
        forked_from_item_id TEXT
      );

      CREATE TABLE turns (
        id TEXT PRIMARY KEY,
        thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
        status TEXT NOT NULL,
        created_at TEXT NOT NULL,
        completed_at TEXT
      );

      CREATE TABLE runs (
        id TEXT PRIMARY KEY,
        thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
        turn_id TEXT NOT NULL REFERENCES turns(id) ON DELETE CASCADE,
        status TEXT NOT NULL,
        execution_mode TEXT NOT NULL,
        wait_reason TEXT NOT NULL,
        graph_id TEXT,
        graph_revision INTEGER,
        node_id TEXT,
        phase_name TEXT,
        phase_index INTEGER,
        created_at TEXT NOT NULL,
        updated_at TEXT NOT NULL,
        started_at TEXT NOT NULL,
        ended_at TEXT
      );
    `);
    database
      .prepare(
        `INSERT INTO threads (
           id, created_at, updated_at, cwd, mode, metadata, title, status, archived_at, forked_from_thread_id, forked_from_item_id
         ) VALUES (?, ?, ?, ?, 'default', '{}', ?, 'active', NULL, NULL, NULL)`,
      )
      .run(
        "thread_legacy",
        "2026-04-10T00:00:00.000Z",
        "2026-04-10T00:00:00.000Z",
        storageRoot,
        "legacy thread",
      );
    database
      .prepare(
        `INSERT INTO turns (id, thread_id, status, created_at, completed_at) VALUES (?, ?, 'running', ?, NULL)`,
      )
      .run("turn_legacy", "thread_legacy", "2026-04-10T00:00:00.000Z");
    database
      .prepare(
        `INSERT INTO runs (
           id, thread_id, turn_id, status, execution_mode, wait_reason, graph_id, graph_revision, node_id,
           phase_name, phase_index, created_at, updated_at, started_at, ended_at
         ) VALUES (?, ?, ?, 'running', 'attached', 'none', NULL, NULL, NULL, 'change', 1, ?, ?, ?, NULL)`,
      )
      .run(
        "run_legacy",
        "thread_legacy",
        "turn_legacy",
        "2026-04-10T00:00:00.000Z",
        "2026-04-10T00:10:00.000Z",
        "2026-04-10T00:00:00.000Z",
      );
    database.close();

    const store = new LocalThreadStore({ storageRoot });
    await expect(store.getRunByTurn("turn_legacy" as never)).resolves.toMatchObject({
      id: "run_legacy",
      phaseName: "change",
      phaseStartedAt: "2026-04-10T00:10:00.000Z",
    });
    store.close();
  });

  it("does not append duplicate attachment blobs", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-attachment-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "attachment blob" });

    const first = await store.putAttachment({
      threadId: thread.id,
      name: "note.txt",
      data: "hello attachment",
    });
    const second = await store.putAttachment({
      threadId: thread.id,
      name: "note.txt",
      data: "hello attachment",
    });

    expect(first.sizeBytes).toBe("hello attachment".length);
    expect(second.sizeBytes).toBe("hello attachment".length);
    const blobKey = createHash("sha256").update("hello attachment").digest("hex");
    expect(readFileSync(join(storageRoot, "blobs", "sha256", blobKey), "utf8")).toBe(
      "hello attachment",
    );

    store.close();
  });

  it("deletes only replay tool input assets", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-replay-asset-delete-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "replay asset delete" });
    const visible = await store.putAttachment({
      threadId: thread.id,
      name: "visible.txt",
      data: "visible attachment",
    });
    const replay = await store.putAttachment({
      threadId: thread.id,
      name: "replay.json",
      data: '{"path":"out.txt","content":"secret"}',
      contentType: REPLAY_TOOL_INPUT_ASSET_CONTENT_TYPE,
    });
    const replayBlobKey = createHash("sha256")
      .update('{"path":"out.txt","content":"secret"}')
      .digest("hex");

    await store.deleteReplayToolInputAsset(visible.id);
    expect(await store.readAssetData(visible.id)).toEqual(Buffer.from("visible attachment"));

    await store.deleteReplayToolInputAsset(replay.id);
    expect(await store.readAssetData(replay.id)).toBeUndefined();
    expect(existsSync(join(storageRoot, "blobs", "sha256", replayBlobKey))).toBe(false);

    store.close();
  });

  it("persists graph-backed plan state with nodes and contracts", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-plan-state-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({
      cwd: storageRoot,
      mode: "plan",
      title: "plan state",
    });
    const plan: PlanProjection = {
      acceptanceCriteria: ["All steps accounted for"],
      affectedPaths: ["docs/architecture"],
      approvedRevision: 2,
      evidenceRefs: ["evidence-1"],
      graphId: "graph-plan-1",
      graphRevision: 3,
      openQuestions: ["Should approval reset after edits?"],
      risks: ["Review drift"],
      status: "review_ready",
      summary: "Graph-backed plan projection",
      threadId: thread.id,
      title: "Kernel Cutover Plan",
      todos: [
        {
          content: "Define protocol shapes",
          dependsOn: [],
          id: "todo-1",
          kind: "decision",
          owner: "parent",
          status: "pending",
        },
        {
          content: "Persist plan state in thread store",
          dependsOn: ["todo-1"],
          id: "todo-2",
          kind: "implementation",
          owner: "parent",
          status: "pending",
        },
      ],
      updatedAt: new Date().toISOString(),
    };
    const contract: NodeContract = {
      allowedTools: ["workspace-search"],
      budget: { maxSteps: 1 },
      capabilityRequirements: [],
      closurePolicy: {
        deterministicSufficient: false,
        humanVerdictRequired: false,
        modelVerdictAllowed: true,
        onInconclusive: "reopen",
        requiredAgreement: "any-one",
      },
      joinPolicy: {
        allowPartialSuccess: false,
        mode: "all-of",
        onPendingOrUnresolved: "block-parent",
        requiresDeterministicMerge: false,
        successCondition: "all child work is complete",
      },
      nodeId: "todo-1",
      preferredRoutingRole: "plan",
      purpose: "Plan the first implementation slice",
      requiredEvidenceKinds: ["handoff-fact"],
      writeScope: { mode: "read-only", paths: [] },
    };

    await store.savePlanState({ nodeContracts: [contract], plan });

    const state = await store.getPlanState(thread.id);
    expect(state).toMatchObject({
      plan: {
        approvedRevision: 2,
        graphId: "graph-plan-1",
        graphRevision: 3,
        title: "Kernel Cutover Plan",
      },
      nodes: [
        expect.objectContaining({
          contract: expect.objectContaining({
            nodeId: "todo-1",
            purpose: "Plan the first implementation slice",
          }),
          graphRevision: 3,
          nodeId: "todo-1",
          position: 0,
        }),
        expect.objectContaining({
          contract: undefined,
          graphRevision: 3,
          nodeId: "todo-2",
          position: 1,
        }),
      ],
    });
  });

  it("stores append-only verdict records for plan nodes", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-verdicts-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "verdict state" });
    const verdictA: VerdictRecord = {
      applicationState: "applied",
      createdAt: new Date("2026-04-10T00:00:00.000Z").toISOString(),
      graphId: "graph-verdict-1",
      graphRevision: 1,
      nodeId: "todo-1",
      source: "model",
      status: "satisfied",
      summary: "Model believes the node is complete.",
      supportsEvidenceIds: ["evidence-1"],
      verdictId: "verdict-1",
    };
    const verdictB: VerdictRecord = {
      applicationState: "superseded",
      createdAt: new Date("2026-04-10T00:00:01.000Z").toISOString(),
      graphId: "graph-verdict-1",
      graphRevision: 1,
      nodeId: "todo-1",
      source: "deterministic",
      status: "unsatisfied",
      summary: "Deterministic verification failed.",
      supersededByVerdictId: "verdict-3",
      supportsEvidenceIds: ["evidence-2"],
      verdictId: "verdict-2",
    };

    await store.appendVerdictRecord({ threadId: thread.id, verdict: verdictA });
    await store.appendVerdictRecord({ threadId: thread.id, verdict: verdictB });

    expect(await store.listVerdictRecords(thread.id, "todo-1")).toEqual([verdictA, verdictB]);
  });

  it("resolves the effective verdict by priority and latest record while ignoring superseded entries", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-effective-verdict-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "effective verdict" });
    const nodeId = "todo-1";
    const verdicts: VerdictRecord[] = [
      {
        applicationState: "applied",
        createdAt: new Date("2026-04-10T00:00:00.000Z").toISOString(),
        graphId: "graph-verdict-1",
        graphRevision: 1,
        nodeId,
        source: "model",
        status: "satisfied",
        summary: "Model says the node is done.",
        supportsEvidenceIds: ["evidence-1"],
        verdictId: "verdict-1",
      },
      {
        applicationState: "applied",
        createdAt: new Date("2026-04-10T00:00:01.000Z").toISOString(),
        graphId: "graph-verdict-1",
        graphRevision: 1,
        nodeId,
        source: "human",
        status: "unsatisfied",
        summary: "Human review says it is not ready.",
        supportsEvidenceIds: ["evidence-2"],
        verdictId: "verdict-2",
      },
      {
        applicationState: "superseded",
        createdAt: new Date("2026-04-10T00:00:02.000Z").toISOString(),
        graphId: "graph-verdict-1",
        graphRevision: 1,
        nodeId,
        source: "deterministic",
        status: "satisfied",
        summary: "Old deterministic pass.",
        supersededByVerdictId: "verdict-4",
        supportsEvidenceIds: ["evidence-3"],
        verdictId: "verdict-3",
      },
      {
        applicationState: "applied",
        createdAt: new Date("2026-04-10T00:00:03.000Z").toISOString(),
        graphId: "graph-verdict-1",
        graphRevision: 1,
        nodeId,
        source: "deterministic",
        status: "unsatisfied",
        summary: "Deterministic verification failed.",
        supportsEvidenceIds: ["evidence-4"],
        verdictId: "verdict-4",
      },
    ];

    for (const verdict of verdicts) {
      await store.appendVerdictRecord({ threadId: thread.id, verdict });
    }

    expect(await store.getEffectiveVerdict(thread.id, nodeId)).toEqual(verdicts[3]);
  });

  it("resolves batched effective verdicts by node id", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-effective-verdict-batch-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({
      cwd: storageRoot,
      title: "batched effective verdicts",
    });
    const nodeA = "todo-a";
    const nodeB = "todo-b";
    const verdicts: VerdictRecord[] = [
      {
        applicationState: "applied",
        createdAt: new Date("2026-04-10T00:00:00.000Z").toISOString(),
        graphId: "graph-verdict-batch",
        graphRevision: 1,
        nodeId: nodeA,
        source: "model",
        status: "satisfied",
        summary: "model says a is done",
        supportsEvidenceIds: [],
        verdictId: "verdict-a-1",
      },
      {
        applicationState: "applied",
        createdAt: new Date("2026-04-10T00:00:01.000Z").toISOString(),
        graphId: "graph-verdict-batch",
        graphRevision: 1,
        nodeId: nodeA,
        source: "deterministic",
        status: "unsatisfied",
        summary: "deterministic says a failed",
        supportsEvidenceIds: [],
        verdictId: "verdict-a-2",
      },
      {
        applicationState: "applied",
        createdAt: new Date("2026-04-10T00:00:02.000Z").toISOString(),
        graphId: "graph-verdict-batch",
        graphRevision: 1,
        nodeId: nodeB,
        source: "human",
        status: "unsatisfied",
        summary: "human says b is not ready",
        supportsEvidenceIds: [],
        verdictId: "verdict-b-1",
      },
      {
        applicationState: "superseded",
        createdAt: new Date("2026-04-10T00:00:03.000Z").toISOString(),
        graphId: "graph-verdict-batch",
        graphRevision: 1,
        nodeId: nodeB,
        source: "deterministic",
        status: "satisfied",
        summary: "old deterministic pass for b",
        supersededByVerdictId: "verdict-b-3",
        supportsEvidenceIds: [],
        verdictId: "verdict-b-2",
      },
      {
        applicationState: "applied",
        createdAt: new Date("2026-04-10T00:00:04.000Z").toISOString(),
        graphId: "graph-verdict-batch",
        graphRevision: 1,
        nodeId: nodeB,
        source: "deterministic",
        status: "satisfied",
        summary: "latest deterministic pass for b",
        supportsEvidenceIds: [],
        verdictId: "verdict-b-3",
      },
    ];

    for (const verdict of verdicts) {
      await store.appendVerdictRecord({ threadId: thread.id, verdict });
    }

    expect(await store.getEffectiveVerdicts(thread.id, [nodeA, nodeB, "missing-node"])).toEqual(
      new Map([
        [nodeA, verdicts[1]],
        [nodeB, verdicts[4]],
      ]),
    );
  });

  it("forks transcript items without carrying over running turn state", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-fork-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "fork source" });
    const firstTurn = await store.createTurn({ threadId: thread.id });
    await store.appendItems([
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: firstTurn.id,
        createdAt: new Date().toISOString(),
        kind: "message",
        role: "user",
        text: "first",
      },
    ]);
    await store.updateTurnStatus(firstTurn.id, "completed");

    const runningTurn = await store.createTurn({ threadId: thread.id });
    await store.appendItems([
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: runningTurn.id,
        createdAt: new Date().toISOString(),
        kind: "message",
        role: "user",
        text: "still running",
      },
      {
        id: createRandomId("item"),
        threadId: thread.id,
        turnId: runningTurn.id,
        createdAt: new Date().toISOString(),
        kind: "tool-call",
        callId: createRandomId("item") as never,
        toolName: "workspace-files",
        input: "0:10",
      },
    ]);

    const forked = await store.forkThread({ threadId: thread.id, title: "forked thread" });
    const forkedTurns = await store.listTurns(forked.id);

    expect(forkedTurns.map((turn) => turn.status).sort()).toEqual(["completed", "interrupted"]);
    expect(forkedTurns.some((turn) => turn.status === "running" || turn.status === "blocked")).toBe(
      false,
    );

    const history = await store.readHistory({ threadId: forked.id, limit: 100 });
    expect(
      history.items.some(
        (item) => item.kind === "message" && item.role === "user" && item.text === "still running",
      ),
    ).toBe(true);
    expect(
      history.items.some(
        (item) => item.kind === "tool-call" && item.toolName === "workspace-files",
      ),
    ).toBe(true);
  });

  it("fences stale attempts at the store write boundary", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-fencing-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "attempt fencing" });
    const turn = await store.createTurn({ threadId: thread.id });
    const run = await store.createRun({
      threadId: thread.id,
      turnId: turn.id,
    });
    const staleAttempt = await store.createRunAttempt({
      ownerId: "runtime-a",
      ownerHostId: "host-a",
      runId: run.id,
    });
    const activeAttempt = await store.createRunAttempt({
      ownerId: "runtime-b",
      ownerHostId: "host-b",
      runId: run.id,
    });

    await expect(
      store.appendItems(
        [
          {
            createdAt: new Date().toISOString(),
            id: createRandomId("item"),
            kind: "message",
            role: "agent",
            text: "stale write",
            threadId: thread.id,
            turnId: turn.id,
          },
        ],
        {
          expectedRunAttempts: [
            {
              attemptId: staleAttempt.id,
              ownerId: "runtime-a",
              turnId: turn.id,
            },
          ],
        },
      ),
    ).rejects.toMatchObject({
      code: "RUN_ATTEMPT_FENCED",
    });

    await expect(
      store.updateTurnStatus(turn.id, "failed", undefined, {
        expectedRunAttempt: {
          attemptId: staleAttempt.id,
          ownerId: "runtime-a",
        },
      }),
    ).rejects.toMatchObject({
      code: "RUN_ATTEMPT_FENCED",
    });
    await expect(
      store.updateRun(
        run.id,
        { executionMode: "detached" },
        {
          expectedRunAttempt: {
            attemptId: staleAttempt.id,
            ownerId: "runtime-a",
          },
        },
      ),
    ).rejects.toMatchObject({
      code: "RUN_ATTEMPT_FENCED",
    });
    await expect(
      store.updateRunAttempt(
        staleAttempt.id,
        {},
        {
          expectLatest: true,
          expectedOwnerId: "runtime-a",
        },
      ),
    ).rejects.toMatchObject({
      code: "RUN_ATTEMPT_FENCED",
    });
    await expect(
      store.putArtifact(
        {
          threadId: thread.id,
          turnId: turn.id,
          name: "stale.txt",
          data: "stale artifact",
        },
        {
          expectedRunAttempt: {
            attemptId: staleAttempt.id,
            ownerId: "runtime-a",
          },
        },
      ),
    ).rejects.toMatchObject({
      code: "RUN_ATTEMPT_FENCED",
    });
    await expect(
      store.putAttachment(
        {
          threadId: thread.id,
          turnId: turn.id,
          name: "stale.bin",
          data: "stale attachment",
        },
        {
          expectedRunAttempt: {
            attemptId: staleAttempt.id,
            ownerId: "runtime-a",
          },
        },
      ),
    ).rejects.toMatchObject({
      code: "RUN_ATTEMPT_FENCED",
    });
    await expect(
      store.saveRunCheckpoint(
        {
          attemptId: staleAttempt.id,
          kind: "provider-request",
          payload: {
            kind: "provider-request",
            operation: "generate",
          },
          runId: run.id,
          turnId: turn.id,
        },
        {
          expectedRunAttempt: {
            attemptId: staleAttempt.id,
            ownerId: "runtime-a",
          },
        },
      ),
    ).rejects.toMatchObject({
      code: "RUN_ATTEMPT_FENCED",
    });

    await store.appendItems(
      [
        {
          createdAt: new Date().toISOString(),
          id: createRandomId("item"),
          kind: "message",
          role: "agent",
          text: "active write",
          threadId: thread.id,
          turnId: turn.id,
        },
      ],
      {
        expectedRunAttempts: [
          {
            attemptId: activeAttempt.id,
            ownerId: "runtime-b",
            turnId: turn.id,
          },
        ],
      },
    );
    await store.updateRun(
      run.id,
      { executionMode: "detached" },
      {
        expectedRunAttempt: {
          attemptId: activeAttempt.id,
          ownerId: "runtime-b",
        },
      },
    );
    await store.updateRunAttempt(
      activeAttempt.id,
      {},
      {
        expectLatest: true,
        expectedOwnerId: "runtime-b",
      },
    );
    const checkpoint = await store.saveRunCheckpoint(
      {
        attemptId: activeAttempt.id,
        kind: "provider-request",
        payload: {
          kind: "provider-request",
          operation: "generate",
        },
        runId: run.id,
        turnId: turn.id,
      },
      {
        expectedRunAttempt: {
          attemptId: activeAttempt.id,
          ownerId: "runtime-b",
        },
      },
    );
    await expect(
      store.updateRunCheckpoint(
        checkpoint.id,
        {
          completedAt: "2026-04-08T00:00:00.000Z",
        },
        {
          expectedRunAttempt: {
            attemptId: staleAttempt.id,
            ownerId: "runtime-a",
          },
        },
      ),
    ).rejects.toMatchObject({
      code: "RUN_ATTEMPT_FENCED",
    });
    await store.updateRunCheckpoint(
      checkpoint.id,
      {
        completedAt: "2026-04-08T00:00:00.000Z",
      },
      {
        expectedRunAttempt: {
          attemptId: activeAttempt.id,
          ownerId: "runtime-b",
        },
      },
    );
    await store.putArtifact(
      {
        threadId: thread.id,
        turnId: turn.id,
        name: "active.txt",
        data: "active artifact",
      },
      {
        expectedRunAttempt: {
          attemptId: activeAttempt.id,
          ownerId: "runtime-b",
        },
      },
    );
    const completed = await store.updateTurnStatus(turn.id, "completed", undefined, {
      expectedRunAttempt: {
        attemptId: activeAttempt.id,
        ownerId: "runtime-b",
      },
    });

    expect(completed.status).toBe("completed");
    const history = await store.readHistory({ threadId: thread.id });
    expect(history.items).toEqual([
      expect.objectContaining({
        kind: "message",
        text: "active write",
      }),
    ]);
    expect(await store.listThreadAssets(thread.id)).toEqual([
      expect.objectContaining({
        kind: "artifact",
        name: "active.txt",
      }),
    ]);
    expect(await store.getRun(run.id)).toMatchObject({
      executionMode: "detached",
    });
  });

  it("claims stale attempts atomically and only admits one new owner", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-claim-`);
    storageRoots.push(storageRoot);

    const storeA = new LocalThreadStore({ storageRoot });
    const thread = await storeA.createThread({ cwd: storageRoot, title: "claim stale attempt" });
    const turn = await storeA.createTurn({ threadId: thread.id });
    const run = await storeA.createRun({
      threadId: thread.id,
      turnId: turn.id,
    });
    const staleAttempt = await storeA.createRunAttempt({
      ownerId: "runtime-a",
      ownerHostId: "host-a",
      runId: run.id,
    });
    await storeA.updateRunAttempt(staleAttempt.id, {
      lastHeartbeatAt: "2000-01-01T00:00:00.000Z",
    });

    const storeB = new LocalThreadStore({ storageRoot });
    const storeC = new LocalThreadStore({ storageRoot });
    const staleBefore = new Date(Date.now() - 1).toISOString();
    const [claimB, claimC] = await Promise.all([
      storeB.claimRunAttempt({
        ownerId: "runtime-b",
        ownerHostId: "host-b",
        staleBefore,
        turnId: turn.id,
      }),
      storeC.claimRunAttempt({
        ownerId: "runtime-c",
        ownerHostId: "host-c",
        staleBefore,
        turnId: turn.id,
      }),
    ]);

    const successfulClaims = [claimB, claimC].filter(
      (attempt): attempt is NonNullable<typeof attempt> => Boolean(attempt),
    );
    expect(successfulClaims).toHaveLength(1);
    expect(successfulClaims[0]).toMatchObject({
      leaseEpoch: 2,
      takeoverCount: 1,
      status: "running",
    });

    const attempts = await storeA.listRunAttempts(run.id);
    expect(attempts).toHaveLength(2);
    expect(attempts[0]).toMatchObject({
      id: staleAttempt.id,
      ownerId: "runtime-a",
      status: "abandoned",
    });
    expect(attempts[1]).toMatchObject({
      id: successfulClaims[0].id,
      leaseEpoch: 2,
      ownerId: successfulClaims[0].ownerId,
      ownerHostId: successfulClaims[0].ownerHostId,
      status: "running",
      takeoverCount: 1,
    });

    storeB.close();
    storeC.close();
  });

  it("allows immediate attempt takeover for paused runs and persists pending checkpoints", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-paused-checkpoint-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "paused checkpoint" });
    const turn = await store.createTurn({ threadId: thread.id });
    const run = await store.createRun({
      threadId: thread.id,
      turnId: turn.id,
    });
    const attempt = await store.createRunAttempt({
      ownerId: "runtime-a",
      ownerHostId: "host-a",
      runId: run.id,
    });
    await store.updateRun(run.id, {
      status: "paused",
      waitReason: "approval",
    });

    const checkpoint = await store.saveRunCheckpoint({
      approvalId: "approval_1" as never,
      attemptId: attempt.id,
      kind: "approval-wait",
      payload: {
        approval: {
          id: "approval_1",
        },
        kind: "approval",
      },
      runId: run.id,
      turnId: turn.id,
    });

    expect(await store.getPendingRunCheckpointByTurn(turn.id)).toMatchObject({
      id: checkpoint.id,
      kind: "approval-wait",
    });
    expect(await store.getPendingRunCheckpointByApprovalId("approval_1" as never)).toMatchObject({
      id: checkpoint.id,
    });

    const claimed = await store.claimRunAttempt({
      ownerId: "runtime-b",
      ownerHostId: "host-b",
      staleBefore: new Date(Date.now() - 1).toISOString(),
      turnId: turn.id,
    });
    expect(claimed).toMatchObject({
      leaseEpoch: 2,
      ownerId: "runtime-b",
      ownerHostId: "host-b",
      status: "running",
      takeoverCount: 1,
    });

    const attempts = await store.listRunAttempts(run.id);
    expect(attempts).toHaveLength(2);
    expect(attempts[0]).toMatchObject({
      id: attempt.id,
      status: "abandoned",
    });
    expect(attempts[1]).toMatchObject({
      id: claimed?.id,
      ownerId: "runtime-b",
      status: "running",
    });

    const updated = await store.updateRunCheckpoint(checkpoint.id, {
      completedAt: "2026-04-08T00:00:00.000Z",
      payload: {
        approval: {
          id: "approval_1",
        },
        kind: "approval",
        resolution: {
          decision: "allow",
          scope: "once",
        },
      },
    });
    expect(updated.completedAt).toBe("2026-04-08T00:00:00.000Z");
    expect(await store.getPendingRunCheckpointByTurn(turn.id)).toBeNull();
  });

  it("persists event envelopes and resolves child stream events onto the parent thread", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-events-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const parentThread = await store.createThread({ cwd: storageRoot, title: "parent events" });
    const parentTurn = await store.createTurn({ threadId: parentThread.id });
    const childThread = await store.createThread({ cwd: storageRoot, title: "child events" });
    const childTurn = await store.createTurn({ threadId: childThread.id });

    const started = store.appendEvent({
      type: "turn.started",
      turn: parentTurn,
    });
    const childDelta = store.appendEvent({
      type: "turn.text.delta",
      delta: "child output",
      parentTurnId: parentTurn.id,
      relation: {
        childTaskId: "childtask_1" as never,
        joinPolicy: "apply-disjoint-writes",
        kind: "workflow-phase",
        workflowName: "build-verify",
        phaseName: "build",
        objective: "Build the requested change.",
        ownershipMode: "disjoint-write",
        profileName: "builder",
        correlationId: "corr_1",
      },
      streamId: "stream_1",
      threadId: childThread.id,
      turnId: childTurn.id,
    });

    expect(started.sequence).toBe(1);
    expect(started.id).toEqual(expect.any(String));
    expect(started.createdAt).toEqual(expect.any(String));
    expect(started.schemaVersion).toBe(1);
    expect(started.threadId).toBe(parentThread.id);
    expect(childDelta.sequence).toBe(2);
    expect(childDelta.threadId).toBe(parentThread.id);
    expect(childDelta.turnId).toBe(childTurn.id);

    const events = await store.readEvents({ threadId: parentThread.id });
    expect(events.latestCursor).toEqual({ sequence: 2, threadId: parentThread.id });
    expect(events.latestSequence).toBe(2);
    expect(events.events).toEqual([
      expect.objectContaining({
        createdAt: expect.any(String),
        event: expect.objectContaining({ type: "turn.started" }),
        id: expect.any(String),
        sequence: 1,
        threadId: parentThread.id,
        schemaVersion: 1,
      }),
      expect.objectContaining({
        createdAt: expect.any(String),
        event: expect.objectContaining({
          type: "turn.text.delta",
          delta: "child output",
          parentTurnId: parentTurn.id,
        }),
        id: expect.any(String),
        sequence: 2,
        threadId: parentThread.id,
        turnId: childTurn.id,
        schemaVersion: 1,
      }),
    ]);

    const catchUp = await store.readEvents({
      after: events.latestCursor,
      threadId: parentThread.id,
    });
    expect(catchUp.events).toEqual([]);
    expect(catchUp.latestCursor).toEqual(events.latestCursor);
  });

  it("persists projection snapshots per thread and kind", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-projections-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "projection snapshots" });

    const first = await store.saveProjectionSnapshot({
      kind: "thread-events",
      sequence: 3,
      snapshot: {
        latestSequence: 3,
      },
      threadId: thread.id,
    });
    const second = await store.saveProjectionSnapshot({
      kind: "thread-runs",
      sequence: 5,
      snapshot: {
        runCount: 2,
      },
      status: "rebuilding",
      threadId: thread.id,
    });
    const updated = await store.saveProjectionSnapshot({
      kind: "thread-events",
      sequence: 7,
      snapshot: {
        latestSequence: 7,
      },
      threadId: thread.id,
    });

    expect(first.kind).toBe("thread-events");
    expect(first.sequence).toBe(3);
    expect(updated.createdAt).toBe(first.createdAt);
    expect(updated.sequence).toBe(7);
    expect(Date.parse(updated.updatedAt)).toBeGreaterThanOrEqual(Date.parse(first.updatedAt));
    expect(second.status).toBe("rebuilding");

    const snapshots = await store.listProjectionSnapshots(thread.id);
    expect(snapshots).toHaveLength(2);
    expect(snapshots).toEqual(
      expect.arrayContaining([
        expect.objectContaining({
          kind: "thread-events",
          schemaVersion: 1,
          sequence: 7,
          snapshot: {
            latestSequence: 7,
          },
          status: "healthy",
          threadId: thread.id,
        }),
        expect.objectContaining({
          kind: "thread-runs",
          schemaVersion: 1,
          sequence: 5,
          snapshot: {
            runCount: 2,
          },
          status: "rebuilding",
          threadId: thread.id,
        }),
      ]),
    );
  });

  it("persists worker leases by checkpoint step id", async () => {
    const storageRoot = mkdtempSync(`${tmpdir()}/best-agent-thread-store-worker-leases-`);
    storageRoots.push(storageRoot);

    const store = new LocalThreadStore({ storageRoot });
    const thread = await store.createThread({ cwd: storageRoot, title: "worker leases" });
    const turn = await store.createTurn({ threadId: thread.id });
    const run = await store.createRun({
      threadId: thread.id,
      turnId: turn.id,
    });
    const attempt = await store.createRunAttempt({
      ownerId: "runtime-a",
      ownerHostId: "host-a",
      runId: run.id,
    });
    const checkpoint = await store.saveRunCheckpoint({
      attemptId: attempt.id,
      kind: "provider-request",
      payload: {
        kind: "provider-request",
        operation: "generate",
      },
      runId: run.id,
      turnId: turn.id,
    });

    const lease = await store.createWorkerLease({
      attemptId: attempt.id,
      kind: "provider",
      ownerHostId: "host-a",
      runId: run.id,
      stepId: checkpoint.id,
      threadId: thread.id,
      turnId: turn.id,
    });
    expect(lease).toMatchObject({
      attemptId: attempt.id,
      kind: "provider",
      leaseEpoch: 1,
      ownerHostId: "host-a",
      runId: run.id,
      status: "running",
      stepId: checkpoint.id,
      threadId: thread.id,
      turnId: turn.id,
    });

    const updated = await store.updateWorkerLease(checkpoint.id, {
      endedAt: "2026-04-09T00:00:00.000Z",
      status: "completed",
    });
    expect(updated).toMatchObject({
      endedAt: "2026-04-09T00:00:00.000Z",
      status: "completed",
      stepId: checkpoint.id,
    });
    expect(await store.listWorkerLeases(thread.id)).toEqual([
      expect.objectContaining({
        id: lease.id,
        status: "completed",
        stepId: checkpoint.id,
      }),
    ]);
  });
});
