import { createHash } from "crypto";
import { mkdtemp, mkdir, readFile, readdir, realpath, rm, stat, symlink, writeFile } from "fs/promises";
import { basename, dirname, join, resolve } from "path";
import { tmpdir } from "os";
import { afterEach, describe, expect, it, vi } from "vitest";
import { cleanupExpiredArtifacts, createArtifactBundle, type ArtifactManifest } from "../src/providers/artifacts";
import {
  workflowTestUtils,
  runInspiredesignWorkflow,
  runProductVideoWorkflow,
  runResearchWorkflow,
  runShoppingWorkflow,
  type ProviderExecutor
} from "../src/providers/workflows";
import type { ProviderAggregateResult } from "../src/providers/types";
import { installExpectedProviderWarnCapture } from "./support/provider-warn-capture";

const makeAggregate = (overrides: Partial<ProviderAggregateResult> = {}): ProviderAggregateResult => ({
  ok: true,
  records: [],
  trace: { requestId: "req-1", ts: "2026-02-16T00:00:00.000Z" },
  partial: false,
  failures: [],
  metrics: { attempted: 1, succeeded: 1, failed: 0, retries: 0, latencyMs: 1 },
  sourceSelection: "web",
  providerOrder: ["web/default"],
  ...overrides
});

const toRuntime = (handlers: {
  search: ProviderExecutor["search"];
  fetch: ProviderExecutor["fetch"];
}): ProviderExecutor => ({
  search: handlers.search,
  fetch: handlers.fetch
});

const summaryHash = (value: string): string => createHash("sha1").update(value).digest("hex").slice(0, 16);

const RESEARCH_ARTIFACT_FILES = [
  "summary.md",
  "report.md",
  "records.json",
  "context.json",
  "meta.json",
  "bundle-manifest.json"
];

installExpectedProviderWarnCapture();

describe("artifact and workflow runtime", () => {
  const createdDirs: string[] = [];

  afterEach(async () => {
    vi.unstubAllGlobals();
    vi.useRealTimers();
    vi.doUnmock("fs/promises");
    vi.resetModules();
    workflowTestUtils.resetProviderSignalState();
    await Promise.all(createdDirs.map(async (directory) => {
      await rm(directory, { recursive: true, force: true });
    }));
    createdDirs.length = 0;
  });

  const makeWorkspaceDir = async (prefix: string): Promise<string> => {
    const directory = await mkdtemp(join(tmpdir(), prefix));
    createdDirs.push(directory);
    return directory;
  };

  const expectArtifactPath = (artifactPath: string, root: string, namespace: string): void => {
    expect(resolve(dirname(dirname(artifactPath)))).toBe(resolve(root));
    expect(basename(dirname(artifactPath))).toBe(namespace);
    expect(basename(artifactPath)).toMatch(/^[0-9a-f-]{36}$/);
  };

  it("writes artifact bundles and cleans expired runs", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-"));
    createdDirs.push(root);

    const expired = await createArtifactBundle({
      namespace: "research",
      outputDir: root,
      ttlHours: 1,
      now: new Date("2026-02-01T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    });

    const active = await createArtifactBundle({
      namespace: "research",
      outputDir: root,
      ttlHours: 24,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    });

    expect(await stat(join(expired.basePath, "bundle-manifest.json"))).toBeDefined();
    const cleaned = await cleanupExpiredArtifacts(root, new Date("2026-02-16T12:00:00.000Z"));
    expect(cleaned.removed.some((entry) => entry.includes(expired.runId))).toBe(true);
    expect(cleaned.skipped.some((entry) => entry.includes(active.runId))).toBe(true);
  });

  it("uses explicit temporary artifact roots for direct bundles", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-explicit-"));
    createdDirs.push(root);

    const bundle = await createArtifactBundle({
      namespace: "research",
      outputDir: root,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    });

    expectArtifactPath(bundle.basePath, root, "research");
    expect(await stat(join(bundle.basePath, "bundle-manifest.json"))).toBeDefined();
  });

  it("rejects omitted direct bundle output roots at runtime", async () => {
    const missingOutputDirArgs = {
      namespace: "research",
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    } as Parameters<typeof createArtifactBundle>[0];

    await expect(createArtifactBundle(missingOutputDirArgs)).rejects.toThrow("outputDir is required");
  });

  it("rejects non-string direct bundle output roots at runtime", async () => {
    await expect(createArtifactBundle({
      namespace: "research",
      outputDir: null as never,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("outputDir is required");
    await expect(createArtifactBundle({
      namespace: "research",
      outputDir: 42 as never,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("outputDir is required");
  });

  it("rejects empty direct bundle output roots", async () => {
    await expect(createArtifactBundle({
      namespace: "research",
      outputDir: "",
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("outputDir cannot be empty");
  });

  it("rejects whitespace direct bundle output roots", async () => {
    await expect(createArtifactBundle({
      namespace: "research",
      outputDir: "   ",
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("outputDir cannot be empty");
  });

  it.each([
    "",
    "   ",
    ".",
    "..",
    "../research",
    "/tmp/research",
    "research/nested",
    "research\\nested",
    "research.name"
  ])("rejects unsafe artifact namespace %s", async (namespace) => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-unsafe-namespace-"));
    createdDirs.push(root);

    await expect(createArtifactBundle({
      namespace,
      outputDir: root,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("Artifact namespace must be a safe path segment");
  });

  it("rejects symlinked bundle output roots before writing files", async () => {
    const targetRoot = await mkdtemp(join(tmpdir(), "odb-artifacts-output-target-"));
    const linkParent = await mkdtemp(join(tmpdir(), "odb-artifacts-output-link-"));
    createdDirs.push(targetRoot, linkParent);
    const linkedRoot = join(linkParent, ".opendevbrowser");
    await symlink(targetRoot, linkedRoot, "dir");

    await expect(createArtifactBundle({
      namespace: "research",
      outputDir: linkedRoot,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("Artifact output directory must be a real directory");
    expect(await readdir(targetRoot)).toEqual([]);
  });

  it("rejects symlinked bundle namespaces before writing files", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-namespace-link-root-"));
    const targetNamespace = await mkdtemp(join(tmpdir(), "odb-artifacts-namespace-link-target-"));
    createdDirs.push(root, targetNamespace);
    await symlink(targetNamespace, join(root, "research"), "dir");

    await expect(createArtifactBundle({
      namespace: "research",
      outputDir: root,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("Artifact namespace directory must be a real directory");
    expect(await readdir(targetNamespace)).toEqual([]);
  });

  it("rejects namespace directories whose real path escapes the output root", async () => {
    const root = "/virtual-artifacts-create-namespace";
    const namespacePath = join(root, "research");
    vi.doMock("fs/promises", () => ({
      mkdir: vi.fn(async () => undefined),
      lstat: vi.fn(async (target: string) => {
        if (target === root || target === namespacePath) {
          return {
            isDirectory: () => true,
            isFile: () => false,
            isSymbolicLink: () => false
          };
        }
        throw new Error(`unexpected lstat ${target}`);
      }),
      realpath: vi.fn(async (target: string) => target === namespacePath ? "/outside/research" : target),
      readdir: vi.fn(async () => []),
      readFile: vi.fn(async () => ""),
      rm: vi.fn(async () => undefined),
      writeFile: vi.fn(async () => undefined)
    }));

    const mockedFs = await import("fs/promises");
    const { createArtifactBundle: createBundleWithMocks } = await import("../src/providers/artifacts");
    await expect(createBundleWithMocks({
      namespace: "research",
      outputDir: root,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("Artifact namespace directory must stay inside output directory");
    expect(mockedFs.writeFile).not.toHaveBeenCalled();
  });

  it("rejects bundle directories that are replaced before file writes", async () => {
    vi.doMock("crypto", () => ({ randomUUID: () => "00000000-0000-4000-8000-000000000000" }));
    const root = "/virtual-artifacts-create-base";
    const namespacePath = join(root, "research");
    const basePath = join(namespacePath, "00000000-0000-4000-8000-000000000000");
    vi.doMock("fs/promises", () => ({
      mkdir: vi.fn(async () => undefined),
      lstat: vi.fn(async (target: string) => {
        if (target === root || target === namespacePath) {
          return {
            isDirectory: () => true,
            isFile: () => false,
            isSymbolicLink: () => false
          };
        }
        if (target === basePath) {
          return {
            isDirectory: () => false,
            isFile: () => true,
            isSymbolicLink: () => false
          };
        }
        throw new Error(`unexpected lstat ${target}`);
      }),
      realpath: vi.fn(async (target: string) => target),
      readdir: vi.fn(async () => []),
      readFile: vi.fn(async () => ""),
      rm: vi.fn(async () => undefined),
      writeFile: vi.fn(async () => undefined)
    }));

    const mockedFs = await import("fs/promises");
    const { createArtifactBundle: createBundleWithMocks } = await import("../src/providers/artifacts");
    await expect(createBundleWithMocks({
      namespace: "research",
      outputDir: root,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("Artifact bundle directory must be a real directory");
    expect(mockedFs.writeFile).not.toHaveBeenCalled();
  });

  it("rejects bundle directories whose real path escapes the namespace", async () => {
    vi.doMock("crypto", () => ({ randomUUID: () => "11111111-1111-4111-8111-111111111111" }));
    const root = "/virtual-artifacts-create-base-escape";
    const namespacePath = join(root, "research");
    const basePath = join(namespacePath, "11111111-1111-4111-8111-111111111111");
    vi.doMock("fs/promises", () => ({
      mkdir: vi.fn(async () => undefined),
      lstat: vi.fn(async (target: string) => {
        if (target === root || target === namespacePath || target === basePath) {
          return {
            isDirectory: () => true,
            isFile: () => false,
            isSymbolicLink: () => false
          };
        }
        throw new Error(`unexpected lstat ${target}`);
      }),
      realpath: vi.fn(async (target: string) => target === basePath ? "/outside/run" : target),
      readdir: vi.fn(async () => []),
      readFile: vi.fn(async () => ""),
      rm: vi.fn(async () => undefined),
      writeFile: vi.fn(async () => undefined)
    }));

    const mockedFs = await import("fs/promises");
    const { createArtifactBundle: createBundleWithMocks } = await import("../src/providers/artifacts");
    await expect(createBundleWithMocks({
      namespace: "research",
      outputDir: root,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "summary" }]
    })).rejects.toThrow("Artifact bundle directory must stay inside output directory");
    expect(mockedFs.writeFile).not.toHaveBeenCalled();
  });

  it("writes nested safe artifact file paths", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-nested-"));
    createdDirs.push(root);

    const bundle = await createArtifactBundle({
      namespace: "research",
      outputDir: root,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: "reports/summary.md", content: "summary" }]
    });

    const manifest = JSON.parse(
      await readFile(join(bundle.basePath, "bundle-manifest.json"), "utf8")
    ) as ArtifactManifest;
    expect(await readFile(join(bundle.basePath, "reports", "summary.md"), "utf8")).toBe("summary");
    expect(manifest.files).toEqual(["reports/summary.md", "bundle-manifest.json"]);
  });

  it.each([
    "",
    "   ",
    ".",
    "reports/./summary.md",
    "../escape.md",
    "reports/../escape.md",
    "/tmp/escape.md",
    String.raw`reports\summary.md`
  ])("rejects unsafe artifact file path %s", async (artifactPath) => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-unsafe-path-"));
    createdDirs.push(root);

    await expect(createArtifactBundle({
      namespace: "research",
      outputDir: root,
      now: new Date("2026-02-16T00:00:00.000Z"),
      files: [{ path: artifactPath, content: "summary" }]
    })).rejects.toThrow("Artifact file path must be a safe relative path");
  });

  it("skips invalid manifest layouts and non-expiring manifests safely", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-layout-"));
    createdDirs.push(root);

    const namespaceDir = join(root, "research");
    const missingManifestRun = join(namespaceDir, "missing-manifest");
    const manifestAsDirectoryRun = join(namespaceDir, "manifest-as-directory");
    const invalidExpiryRun = join(namespaceDir, "invalid-expiry");

    await mkdir(missingManifestRun, { recursive: true });
    await mkdir(join(manifestAsDirectoryRun, "bundle-manifest.json"), { recursive: true });
    await mkdir(invalidExpiryRun, { recursive: true });
    await writeFile(join(invalidExpiryRun, "bundle-manifest.json"), JSON.stringify({
      run_id: "invalid-expiry",
      created_at: "2026-02-16T00:00:00.000Z",
      ttl_hours: 24,
      expires_at: "not-a-date",
      files: []
    }));

    const cleaned = await cleanupExpiredArtifacts(root, new Date("2026-02-20T00:00:00.000Z"));
    expect(cleaned.removed).toHaveLength(0);
    expect(cleaned.skipped).toContain(missingManifestRun);
    expect(cleaned.skipped).toContain(manifestAsDirectoryRun);
    expect(cleaned.skipped).toContain(invalidExpiryRun);
  });

  it("handles missing cleanup roots and corrupt artifact manifests safely", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-corrupt-"));
    createdDirs.push(root);
    const corruptRun = join(root, "research", "corrupt-manifest");

    await writeFile(join(root, "not-a-namespace"), "artifact namespace placeholder");
    await mkdir(corruptRun, { recursive: true });
    await writeFile(join(corruptRun, "bundle-manifest.json"), "{not-json");

    await expect(cleanupExpiredArtifacts(join(root, "missing"), new Date("2026-02-20T00:00:00.000Z"))).resolves.toEqual({
      removed: [],
      skipped: []
    });
    await expect(cleanupExpiredArtifacts(root, new Date("2026-02-20T00:00:00.000Z"))).resolves.toEqual({
      removed: [],
      skipped: [corruptRun]
    });
  });

  it("does not treat product manifests as cleanup lifecycle manifests", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-product-manifest-"));
    createdDirs.push(root);
    const runPath = join(root, "product-video", "product-manifest-only");

    await mkdir(runPath, { recursive: true });
    await writeFile(join(runPath, "manifest.json"), JSON.stringify({
      run_id: "product-manifest-only",
      expires_at: "2026-02-01T01:00:00.000Z",
      product: { title: "Widget Pro" }
    }));

    const cleaned = await cleanupExpiredArtifacts(root, new Date("2026-02-16T12:00:00.000Z"));
    expect(cleaned.removed).not.toContain(runPath);
    expect(cleaned.skipped).toContain(runPath);
  });

  it("skips runs when the lifecycle manifest is not a file", async () => {
    const root = "/virtual-artifacts";
    const namespacePath = join(root, "research");
    const runPath = join(namespacePath, "run-1");
    const manifestPath = join(runPath, "bundle-manifest.json");
    vi.doMock("fs/promises", () => ({
      readdir: vi.fn(async (target: string) => {
        if (target === root) {
          return ["research"];
        }
        if (target === namespacePath) {
          return ["run-1"];
        }
        throw new Error(`unexpected path ${target}`);
      }),
      lstat: vi.fn(async (target: string) => {
        if (target === root || target === namespacePath || target === runPath) {
          return {
            isDirectory: () => true,
            isFile: () => false,
            isSymbolicLink: () => false
          };
        }
        if (target !== manifestPath) {
          throw new Error(`unexpected lstat ${target}`);
        }
        return {
          isDirectory: () => false,
          isFile: () => false,
          isSymbolicLink: () => false
        };
      }),
      realpath: vi.fn(async (target: string) => target),
      readFile: vi.fn(async () => {
        throw new Error("read should not occur for non-file manifests");
      }),
      rm: vi.fn(async () => undefined),
      mkdir: vi.fn(async () => undefined),
      writeFile: vi.fn(async () => undefined)
    }));

    const mockedFs = await import("fs/promises");
    const { cleanupExpiredArtifacts: cleanupWithMocks } = await import("../src/providers/artifacts");
    const cleaned = await cleanupWithMocks(root, new Date("2026-02-20T00:00:00.000Z"));

    expect(cleaned).toEqual({
      removed: [],
      skipped: [runPath]
    });
    expect(mockedFs.readFile).not.toHaveBeenCalled();
  });

  it("skips cleanup entries whose resolved paths escape the cleanup root", async () => {
    const root = "/virtual-artifacts-containment";
    const escapedNamespacePath = join(root, "linked-namespace");
    const namespacePath = join(root, "research");
    const runPath = join(namespacePath, "run-1");
    vi.doMock("fs/promises", () => ({
      readdir: vi.fn(async (target: string) => {
        if (target === root) {
          return ["linked-namespace", "research"];
        }
        if (target === namespacePath) {
          return ["run-1"];
        }
        throw new Error(`unexpected path ${target}`);
      }),
      lstat: vi.fn(async (target: string) => {
        if (target === root || target === escapedNamespacePath || target === namespacePath || target === runPath) {
          return {
            isDirectory: () => true,
            isFile: () => false,
            isSymbolicLink: () => false
          };
        }
        throw new Error(`unexpected lstat ${target}`);
      }),
      realpath: vi.fn(async (target: string) => {
        if (target === escapedNamespacePath) {
          return "/outside/linked-namespace";
        }
        if (target === runPath) {
          return "/outside/run-1";
        }
        return target;
      }),
      readFile: vi.fn(async () => {
        throw new Error("read should not occur for escaped cleanup entries");
      }),
      rm: vi.fn(async () => undefined),
      mkdir: vi.fn(async () => undefined),
      writeFile: vi.fn(async () => undefined)
    }));

    const mockedFs = await import("fs/promises");
    const { cleanupExpiredArtifacts: cleanupWithMocks } = await import("../src/providers/artifacts");
    const cleaned = await cleanupWithMocks(root, new Date("2026-02-20T00:00:00.000Z"));

    expect(cleaned).toEqual({
      removed: [],
      skipped: [runPath]
    });
    expect(mockedFs.readFile).not.toHaveBeenCalled();
    expect(mockedFs.rm).not.toHaveBeenCalled();
  });

  it("removes expired cleanup runs by their validated canonical path", async () => {
    const root = "/virtual-artifacts-canonical";
    const namespacePath = join(root, "research");
    const runPath = join(namespacePath, "run-1");
    const realRunPath = join(namespacePath, "run-real");
    const manifestPath = join(runPath, "bundle-manifest.json");
    vi.doMock("fs/promises", () => ({
      readdir: vi.fn(async (target: string) => {
        if (target === root) {
          return ["research"];
        }
        if (target === namespacePath) {
          return ["run-1"];
        }
        throw new Error(`unexpected path ${target}`);
      }),
      lstat: vi.fn(async (target: string) => {
        if (target === root || target === namespacePath || target === runPath) {
          return {
            isDirectory: () => true,
            isFile: () => false,
            isSymbolicLink: () => false
          };
        }
        if (target === manifestPath) {
          return {
            isDirectory: () => false,
            isFile: () => true,
            isSymbolicLink: () => false
          };
        }
        throw new Error(`unexpected lstat ${target}`);
      }),
      realpath: vi.fn(async (target: string) => {
        if (target === runPath) {
          return realRunPath;
        }
        return target;
      }),
      readFile: vi.fn(async () => JSON.stringify({
        run_id: "run-1",
        created_at: "2026-02-01T00:00:00.000Z",
        ttl_hours: 1,
        expires_at: "2026-02-01T01:00:00.000Z",
        files: ["summary.md", "bundle-manifest.json"]
      })),
      rm: vi.fn(async () => undefined),
      mkdir: vi.fn(async () => undefined),
      writeFile: vi.fn(async () => undefined)
    }));

    const mockedFs = await import("fs/promises");
    const { cleanupExpiredArtifacts: cleanupWithMocks } = await import("../src/providers/artifacts");
    const cleaned = await cleanupWithMocks(root, new Date("2026-02-20T00:00:00.000Z"));

    expect(cleaned).toEqual({
      removed: [runPath],
      skipped: []
    });
    expect(mockedFs.rm).toHaveBeenCalledWith(realRunPath, { recursive: true, force: true });
  });

  it("skips expired cleanup runs whose canonical path changes before deletion", async () => {
    const root = "/virtual-artifacts-race";
    const namespacePath = join(root, "research");
    const runPath = join(namespacePath, "run-1");
    const manifestPath = join(runPath, "bundle-manifest.json");
    let runRealpathCalls = 0;
    vi.doMock("fs/promises", () => ({
      readdir: vi.fn(async (target: string) => {
        if (target === root) {
          return ["research"];
        }
        if (target === namespacePath) {
          return ["run-1"];
        }
        throw new Error(`unexpected path ${target}`);
      }),
      lstat: vi.fn(async (target: string) => {
        if (target === root || target === namespacePath || target === runPath) {
          return {
            isDirectory: () => true,
            isFile: () => false,
            isSymbolicLink: () => false
          };
        }
        if (target === manifestPath) {
          return {
            isDirectory: () => false,
            isFile: () => true,
            isSymbolicLink: () => false
          };
        }
        throw new Error(`unexpected lstat ${target}`);
      }),
      realpath: vi.fn(async (target: string) => {
        if (target === runPath) {
          runRealpathCalls += 1;
          return runRealpathCalls === 1 ? runPath : "/outside/run-1";
        }
        return target;
      }),
      readFile: vi.fn(async () => JSON.stringify({
        run_id: "run-1",
        created_at: "2026-02-01T00:00:00.000Z",
        ttl_hours: 1,
        expires_at: "2026-02-01T01:00:00.000Z",
        files: ["summary.md", "bundle-manifest.json"]
      })),
      rm: vi.fn(async () => undefined),
      mkdir: vi.fn(async () => undefined),
      writeFile: vi.fn(async () => undefined)
    }));

    const mockedFs = await import("fs/promises");
    const { cleanupExpiredArtifacts: cleanupWithMocks } = await import("../src/providers/artifacts");
    const cleaned = await cleanupWithMocks(root, new Date("2026-02-20T00:00:00.000Z"));

    expect(cleaned).toEqual({
      removed: [],
      skipped: [runPath]
    });
    expect(mockedFs.rm).not.toHaveBeenCalled();
  });

  it("skips symlinked cleanup roots without deleting target runs", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-root-target-"));
    const linkParent = await mkdtemp(join(tmpdir(), "odb-artifacts-root-link-"));
    createdDirs.push(root, linkParent);
    const linkedRoot = join(linkParent, ".opendevbrowser");
    const expired = await createArtifactBundle({
      namespace: "research",
      outputDir: root,
      ttlHours: 1,
      now: new Date("2026-02-01T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "expired" }]
    });
    await symlink(root, linkedRoot, "dir");

    const cleaned = await cleanupExpiredArtifacts(linkedRoot, new Date("2026-02-16T12:00:00.000Z"));

    expect(cleaned).toEqual({ removed: [], skipped: [linkedRoot] });
    expect(await stat(expired.basePath)).toBeDefined();
  });

  it("skips symlinked cleanup namespaces without deleting target runs", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-namespace-root-"));
    const outsideRoot = await mkdtemp(join(tmpdir(), "odb-artifacts-namespace-target-"));
    createdDirs.push(root, outsideRoot);
    const expired = await createArtifactBundle({
      namespace: "research",
      outputDir: outsideRoot,
      ttlHours: 1,
      now: new Date("2026-02-01T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "expired" }]
    });
    await symlink(join(outsideRoot, "research"), join(root, "research"), "dir");

    const cleaned = await cleanupExpiredArtifacts(root, new Date("2026-02-16T12:00:00.000Z"));

    expect(cleaned.removed).toEqual([]);
    expect(await stat(expired.basePath)).toBeDefined();
  });

  it("skips symlinked cleanup runs without deleting target runs", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-artifacts-run-root-"));
    const outsideRoot = await mkdtemp(join(tmpdir(), "odb-artifacts-run-target-"));
    createdDirs.push(root, outsideRoot);
    const expired = await createArtifactBundle({
      namespace: "research",
      outputDir: outsideRoot,
      ttlHours: 1,
      now: new Date("2026-02-01T00:00:00.000Z"),
      files: [{ path: "summary.md", content: "expired" }]
    });
    const namespaceDir = join(root, "research");
    const linkedRun = join(namespaceDir, expired.runId);
    await mkdir(namespaceDir, { recursive: true });
    await symlink(expired.basePath, linkedRun, "dir");

    const cleaned = await cleanupExpiredArtifacts(root, new Date("2026-02-16T12:00:00.000Z"));

    expect(cleaned).toEqual({ removed: [], skipped: [linkedRun] });
    expect(await stat(expired.basePath)).toBeDefined();
  });

  it("runs research workflow with strict source resolution and artifacts", async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-02-15T00:00:00.000Z"));
    const pathModeRoot = await makeWorkspaceDir("odb-research-path-mode-");

    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const source = options?.source ?? "web";
        return makeAggregate({
          records: [
            {
              id: `${source}-inside`,
              source: source as "web" | "community" | "social" | "shopping",
              provider: `${source}/default`,
              url: `https://example.com/${source}/inside`,
              title: `${source} inside`,
              content: `${source} content`,
              timestamp: "2026-02-10T00:00:00.000Z",
              confidence: 0.8,
              attributes: {}
            },
            {
              id: `${source}-outside`,
              source: source as "web" | "community" | "social" | "shopping",
              provider: `${source}/default`,
              url: `https://example.com/${source}/outside`,
              title: `${source} outside`,
              content: `${source} content`,
              timestamp: "2025-01-01T00:00:00.000Z",
              confidence: 0.5,
              attributes: {}
            }
          ],
          sourceSelection: source as "web"
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runResearchWorkflow(runtime, {
      topic: "browser automation",
      days: 30,
      sourceSelection: "auto",
      mode: "compact"
    });

    expect(output).toMatchObject({
      mode: "compact",
      artifact_path: expect.any(String)
    });
    expect((output.records as Array<{ id: string }>).every((record) => record.id.includes("inside"))).toBe(true);

    const pathMode = await runResearchWorkflow(runtime, {
      topic: "browser automation",
      from: "2026-02-01T00:00:00.000Z",
      to: "2026-02-16T00:00:00.000Z",
      sourceSelection: "all",
      outputDir: pathModeRoot,
      mode: "path"
    });

    expect(pathMode).toMatchObject({
      mode: "path",
      artifact_path: expect.any(String)
    });
    const pathModeArtifactPath = String(pathMode.artifact_path);
    const pathModeManifest = JSON.parse(
      await readFile(join(pathModeArtifactPath, "bundle-manifest.json"), "utf8")
    ) as ArtifactManifest;
    const pathModeReport = await readFile(join(pathModeArtifactPath, "report.md"), "utf8");
    expectArtifactPath(pathModeArtifactPath, pathModeRoot, "research");
    expect(pathModeManifest.files).toEqual(RESEARCH_ARTIFACT_FILES);
    expect(pathMode.meta).toMatchObject({ artifact_manifest: { files: RESEARCH_ARTIFACT_FILES } });
    expect(pathModeReport).toContain("# Research Report");
    expect(pathModeReport).toContain("web inside");
  });

  it("stores default research artifacts under the workspace research directory", async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-02-15T00:00:00.000Z"));
    const workspaceDir = await makeWorkspaceDir("odb-research-workspace-");
    const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceDir);

    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        expect(options?.suspendedIntent).toMatchObject({
          kind: "workflow.research",
          input: {
            workflow: {
              kind: "research",
              input: { outputDir: join(workspaceDir, ".opendevbrowser") }
            }
          }
        });
        return makeAggregate({
        records: [{
          id: "research-record",
          source: "web",
          provider: "web/default",
          url: "https://example.com/research",
          title: "Research record",
          content: [
            "A concrete research artifact record explains that artifact storage should keep report files under the workspace research directory.",
            "The accepted page gives enough detail for a deterministic report to cite the source URL, preserve records.json as the audit trail, and keep bundle manifests stable.",
            "This content-rich fixture prevents the evidence gate from treating the artifact-path assertion as a failed research run while still checking where report.md is written.",
            "The report remains useful because it ties the accepted record to artifact storage behavior, source evidence, and project-local output expectations.",
            "Agents can inspect the generated report, then open the raw metadata files when they need to audit timestamps, providers, and bundle paths."
          ].join(" "),
          timestamp: "2026-02-10T00:00:00.000Z",
          confidence: 0.9,
          attributes: {}
        }]
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    try {
      const output = await runResearchWorkflow(runtime, {
        topic: "artifact storage",
        days: 30,
        mode: "json"
      });

      const artifactPath = String(output.artifact_path);
      expectArtifactPath(artifactPath, join(workspaceDir, ".opendevbrowser"), "research");
      const manifest = JSON.parse(
        await readFile(join(artifactPath, "bundle-manifest.json"), "utf8")
      ) as ArtifactManifest;
      const report = await readFile(join(artifactPath, "report.md"), "utf8");
      expect(manifest.files).toEqual(RESEARCH_ARTIFACT_FILES);
      expect(report).toContain("# Research Report");
      expect(report).toContain("artifact storage should keep report files under the workspace research directory.");
      expect(report).toContain("https://example.com/research");
    } finally {
      cwdSpy.mockRestore();
    }
  });

  it("persists sanitized research output without shell records", async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-03-15T00:00:00.000Z"));
    const root = await mkdtemp(join(tmpdir(), "odb-research-sanitized-"));
    createdDirs.push(root);

    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const source = options?.source ?? "web";
        if (source === "web") {
          return makeAggregate({
            sourceSelection: "web",
            providerOrder: ["web/default"],
            records: [{
              id: "duckduckgo-shell",
              source: "web",
              provider: "web/default",
              url: "https://html.duckduckgo.com/html",
              title: "https://html.duckduckgo.com/html",
              content: "coffee shop website design inspiration at DuckDuckGo",
              timestamp: "2026-03-10T00:00:00.000Z",
              confidence: 0.7,
              attributes: {
                retrievalPath: "web:search:index"
              }
            }]
          });
        }
        if (source === "community") {
          return makeAggregate({
            sourceSelection: "community",
            providerOrder: ["community/default"],
            records: [{
              id: "reddit-login-shell",
              source: "community",
              provider: "community/default",
              url: "https://www.reddit.com/login",
              title: "https://www.reddit.com/login",
              content: "Welcome to Reddit. Log in to continue.",
              timestamp: "2026-02-10T00:00:00.000Z",
              confidence: 0.6,
              attributes: {
                retrievalPath: "community:fetch:url"
              }
            }]
          });
        }
        return makeAggregate({
          sourceSelection: "social",
          providerOrder: ["social/default"],
          records: [
            {
              id: "reddit-generic-shell",
              source: "social",
              provider: "social/default",
              url: "https://www.reddit.com/answers/example?q=coffee+shop+website+design+inspiration",
              title: "https://www.reddit.com/answers/example?q=coffee+shop+website+design+inspiration",
              content: "Reddit - The heart of the internet. Please wait for verification. Skip to main content.",
              timestamp: "2026-03-10T00:00:00.000Z",
              confidence: 0.6,
              attributes: {
                retrievalPath: "social:fetch:url"
              }
            },
            {
              id: "clean-inspiration-record",
              source: "social",
              provider: "social/default",
              url: "https://studio.example.com/inspiration/coffee-shop",
              title: "Coffee shop website design inspiration",
              content: "A warm editorial coffee shop website with strong typography and photography.",
              timestamp: "2026-03-10T00:00:00.000Z",
              confidence: 0.95,
              attributes: {
                retrievalPath: "social:post:url"
              }
            }
          ]
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runResearchWorkflow(runtime, {
      topic: "coffee shop website design inspiration",
      days: 30,
      sourceSelection: "auto",
      mode: "json",
      outputDir: root
    });

    expect((output.records as Array<{ id: string }>).map((record) => record.id)).toEqual(["clean-inspiration-record"]);
    expect((output.meta as {
      metrics: {
        sanitized_records: number;
        sanitized_reason_distribution: Record<string, number>;
      };
    }).metrics).toMatchObject({
      sanitized_records: 3,
      sanitized_reason_distribution: {
        search_index_shell: 1,
        login_shell: 1,
        search_results_shell: 1
      }
    });

    const artifactPath = output.artifact_path as string;
    const recordsPayload = JSON.parse(await readFile(join(artifactPath, "records.json"), "utf8")) as {
      records: Array<{ id: string }>;
    };
    const metaPayload = JSON.parse(await readFile(join(artifactPath, "meta.json"), "utf8")) as {
      metrics: {
        sanitized_records: number;
      };
    };
    expect(recordsPayload.records.map((record) => record.id)).toEqual(["clean-inspiration-record"]);
    expect(metaPayload.metrics.sanitized_records).toBe(3);
    const report = await readFile(join(artifactPath, "report.md"), "utf8");
    expect(report).toContain("Coffee shop website design inspiration");
    expect(report).toContain("login_shell: 1");
  });

  it("reports provider-limited research runs when usable records survive", async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-03-15T00:00:00.000Z"));
    const root = await mkdtemp(join(tmpdir(), "odb-research-provider-limited-"));
    createdDirs.push(root);

    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate({
        ok: false,
        partial: true,
        sourceSelection: "web",
        providerOrder: ["web/default"],
        metrics: { attempted: 1, succeeded: 0, failed: 1, retries: 0, latencyMs: 5 },
        records: [{
          id: "provider-limited-survivor",
          source: "web",
          provider: "web/default",
          url: "https://research.example.com/provider-limited",
          title: "Provider-limited survivor",
          content: "A usable finding survived despite provider limitations.",
          timestamp: "2026-03-10T00:00:00.000Z",
          confidence: 0.88,
          attributes: {}
        }],
        failures: [{
          provider: "web/default",
          source: "web",
          error: {
            code: "unavailable",
            message: "Live source returned a login shell",
            retryable: false,
            reasonCode: "env_limited"
          }
        }]
      })),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runResearchWorkflow(runtime, {
      topic: "provider-limited research",
      days: 30,
      sourceSelection: "web",
      mode: "json",
      outputDir: root
    });

    const artifactPath = String(output.artifact_path);
    const manifest = JSON.parse(
      await readFile(join(artifactPath, "bundle-manifest.json"), "utf8")
    ) as ArtifactManifest;
    const report = await readFile(join(artifactPath, "report.md"), "utf8");
    expect(manifest.files).toEqual(RESEARCH_ARTIFACT_FILES);
    expect(report).toContain("Provider-limited survivor");
    expect(report).toContain("A usable finding survived despite provider limitations.");
    expect(report).toContain("Live source returned a login shell");
    expect(report).toContain("env_limited");
  });

  it("persists fetched web research pages after search-index shells are sanitized", async () => {
    vi.useFakeTimers();
    vi.setSystemTime(new Date("2026-03-15T00:00:00.000Z"));
    const root = await mkdtemp(join(tmpdir(), "odb-research-web-fetch-"));
    createdDirs.push(root);

    const fetch = vi.fn(async (input) => {
      if (input.url === "https://design.example.com/inspiration") {
        return makeAggregate({
          sourceSelection: "web",
          providerOrder: ["web/default"],
          records: [{
            id: "fetched-design-page",
            source: "web",
            provider: "web/default",
            url: "https://design.example.com/inspiration",
            title: "Coffee shop inspiration",
            content: "A warm editorial coffee shop website with strong typography and photography.",
            timestamp: "2026-03-10T00:00:00.000Z",
            confidence: 0.95,
            attributes: {
              retrievalPath: "web:fetch:url"
            }
          }]
        });
      }
      if (input.url === "https://dribbble.com/tags/coffee-shop-website") {
        return makeAggregate({
          sourceSelection: "web",
          providerOrder: ["web/default"],
          records: [{
            id: "fetched-js-shell",
            source: "web",
            provider: "web/default",
            url: "https://dribbble.com/tags/coffee-shop-website",
            title: "https://dribbble.com/tags/coffee-shop-website",
            content: "JavaScript is disabled. In order to continue, we need to verify that you're not a robot.",
            timestamp: "2026-03-10T00:00:00.000Z",
            confidence: 0.5,
            attributes: {}
          }]
        });
      }
      return makeAggregate();
    });
    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const source = options?.source ?? "web";
        if (source !== "web") {
          return makeAggregate({
            sourceSelection: source as "web",
            providerOrder: [`${source}/default`],
            records: []
          });
        }
        return makeAggregate({
          sourceSelection: "web",
          providerOrder: ["web/default"],
          records: [{
            id: "duckduckgo-shell-link",
            source: "web",
            provider: "web/default",
            url: "https://duckduckgo.com/l?uddg=https%3A%2F%2Fdesign.example.com%2Finspiration",
            title: "https://duckduckgo.com/l?uddg=https%3A%2F%2Fdesign.example.com%2Finspiration",
            content: "coffee shop website design inspiration at DuckDuckGo",
            timestamp: "2026-03-10T00:00:00.000Z",
            confidence: 0.7,
            attributes: {
              retrievalPath: "web:search:index"
            }
          }, {
            id: "duckduckgo-js-shell-link",
            source: "web",
            provider: "web/default",
            url: "https://duckduckgo.com/l?uddg=https%3A%2F%2Fdribbble.com%2Ftags%2Fcoffee-shop-website",
            title: "https://duckduckgo.com/l?uddg=https%3A%2F%2Fdribbble.com%2Ftags%2Fcoffee-shop-website",
            content: "coffee shop website design inspiration at DuckDuckGo",
            timestamp: "2026-03-10T00:00:00.000Z",
            confidence: 0.65,
            attributes: {
              retrievalPath: "web:search:index"
            }
          }]
        });
      }),
      fetch
    });

    const output = await runResearchWorkflow(runtime, {
      topic: "coffee shop website design inspiration",
      days: 30,
      sourceSelection: "auto",
      mode: "json",
      outputDir: root
    });

    expect(fetch).toHaveBeenCalledWith({
      url: "https://design.example.com/inspiration"
    }, expect.objectContaining({
      source: "web"
    }));
    expect((output.records as Array<{ id: string }>).map((record) => record.id)).toEqual(["fetched-design-page"]);

    const artifactPath = output.artifact_path as string;
    const recordsPayload = JSON.parse(await readFile(join(artifactPath, "records.json"), "utf8")) as {
      records: Array<{ id: string }>;
    };
    const metaPayload = JSON.parse(await readFile(join(artifactPath, "meta.json"), "utf8")) as {
      metrics: {
        sanitized_records: number;
        final_records: number;
      };
    };
    expect(recordsPayload.records.map((record) => record.id)).toEqual(["fetched-design-page"]);
    expect(metaPayload.metrics).toMatchObject({
      sanitized_records: 3,
      final_records: 1
    });
  });

  it("runs shopping workflow, normalizes offers, and validates provider input", async () => {
    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const providerId = options?.providerIds?.[0] ?? "shopping/others";
        const price = providerId.includes("amazon") ? 25 : 15;
        return makeAggregate({
          sourceSelection: "shopping",
          providerOrder: [providerId],
          records: [{
            id: providerId,
            source: "shopping",
            provider: providerId,
            url: `https://example.com/${providerId}`,
            title: `Offer ${providerId}`,
            content: `$${price}`,
            timestamp: "2026-02-16T00:00:00.000Z",
            confidence: 0.8,
            attributes: {
              shopping_offer: {
                provider: providerId,
                product_id: `${providerId}-product`,
                title: `Offer ${providerId}`,
                url: `https://example.com/${providerId}`,
                price: {
                  amount: price,
                  currency: "USD",
                  retrieved_at: "2026-02-16T00:00:00.000Z"
                },
                shipping: {
                  amount: 0,
                  currency: "USD",
                  notes: "free"
                },
                availability: "in_stock",
                rating: 4.6,
                reviews_count: 50
              }
            }
          }]
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runShoppingWorkflow(runtime, {
      query: "usb hub",
      providers: ["shopping/amazon", "shopping/others"],
      sort: "lowest_price",
      mode: "json"
    });

    const offers = output.offers as Array<{ provider: string; price: { amount: number } }>;
    expect(offers).toHaveLength(2);
    expect(offers[0]?.price.amount).toBeLessThanOrEqual(offers[1]?.price.amount);

    await expect(runShoppingWorkflow(runtime, {
      query: "usb hub",
      providers: ["invalid-provider"],
      mode: "json"
    })).rejects.toThrow("No valid shopping providers");
  });

  it("stores default shopping artifacts under the workspace shopping directory", async () => {
    const workspaceDir = await makeWorkspaceDir("odb-shopping-workspace-");
    const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceDir);

    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        expect(options?.suspendedIntent).toMatchObject({
          kind: "workflow.shopping",
          input: {
            workflow: {
              kind: "shopping",
              input: { outputDir: join(workspaceDir, ".opendevbrowser") }
            }
          }
        });
        const providerId = options?.providerIds?.[0] ?? "shopping/amazon";
        return makeAggregate({
          sourceSelection: "shopping",
          providerOrder: [providerId],
          records: [{
            id: "shopping-offer",
            source: "shopping",
            provider: providerId,
            url: "https://example.com/shopping-offer",
            title: "Shopping offer",
            content: "$25",
            timestamp: "2026-02-16T00:00:00.000Z",
            confidence: 0.9,
            attributes: {
              shopping_offer: {
                provider: providerId,
                product_id: "shopping-offer",
                title: "Shopping offer",
                url: "https://example.com/shopping-offer",
                price: {
                  amount: 25,
                  currency: "USD",
                  retrieved_at: "2026-02-16T00:00:00.000Z"
                },
                availability: "in_stock"
              }
            }
          }]
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    try {
      const output = await runShoppingWorkflow(runtime, {
        query: "artifact storage",
        providers: ["shopping/amazon"],
        mode: "json"
      });

      const artifactPath = String(output.artifact_path);
      expectArtifactPath(artifactPath, join(workspaceDir, ".opendevbrowser"), "shopping");
      const manifest = JSON.parse(
        await readFile(join(artifactPath, "bundle-manifest.json"), "utf8")
      ) as ArtifactManifest;
      expect(manifest.files).toContain("offers.json");
    } finally {
      cwdSpy.mockRestore();
    }
  });

  it("stores inspiredesign artifacts under the resolved root", async () => {
    const root = await makeWorkspaceDir("odb-inspiredesign-resolved-");
    const requestedRoot = join(root, "nested", "..", "artifacts");
    const resolvedRoot = resolve(root, "artifacts");
    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate()),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runInspiredesignWorkflow(runtime, {
      brief: "Create a resolved-root artifact bundle",
      mode: "path",
      outputDir: requestedRoot
    });

    const artifactPath = String(output.artifact_path);
    expectArtifactPath(artifactPath, resolvedRoot, "inspiredesign");
    const manifest = JSON.parse(
      await readFile(join(artifactPath, "bundle-manifest.json"), "utf8")
    ) as ArtifactManifest;
    expect(manifest.files).toContain("design.md");
    expect((output.meta as { artifact_manifest?: ArtifactManifest }).artifact_manifest).toMatchObject({
      run_id: manifest.run_id,
      files: manifest.files
    });
  });

  it("rejects blank workflow artifact roots", async () => {
    const search = vi.fn(async () => makeAggregate());
    const fetch = vi.fn(async () => makeAggregate());
    const runtime = toRuntime({
      search,
      fetch
    });

    await expect(runResearchWorkflow(runtime, {
      topic: "artifact storage",
      mode: "json",
      outputDir: ""
    })).rejects.toThrow("outputDir cannot be empty");
    await expect(runShoppingWorkflow(runtime, {
      query: "artifact storage",
      mode: "json",
      outputDir: ""
    })).rejects.toThrow("outputDir cannot be empty");
    await expect(runResearchWorkflow(runtime, {
      topic: "artifact storage",
      mode: "json",
      outputDir: "   "
    })).rejects.toThrow("outputDir cannot be empty");
    await expect(runShoppingWorkflow(runtime, {
      query: "artifact storage",
      mode: "json",
      outputDir: "   "
    })).rejects.toThrow("outputDir cannot be empty");
    await expect(runProductVideoWorkflow(runtime, {
      product_url: "https://example.com/product",
      output_dir: "   "
    })).rejects.toThrow("outputDir cannot be empty");
    expect(search).not.toHaveBeenCalled();
    expect(fetch).not.toHaveBeenCalled();
  });

  it("uses direct caller relative workflow artifact roots from the current process directory", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-relative-root-"));
    createdDirs.push(root);
    const previousCwd = process.cwd();
    process.chdir(root);
    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate({
        records: [{
          id: "research-record",
          source: "web",
          provider: "web/search",
          url: "https://example.com/research",
          title: "Research artifact",
          content: "Artifact content",
          timestamp: new Date().toISOString(),
          confidence: 0.9,
          attributes: {}
        }]
      })),
      fetch: vi.fn(async () => makeAggregate())
    });

    try {
      const output = await runResearchWorkflow(runtime, {
        topic: "artifact storage",
        mode: "json",
        outputDir: "direct-artifacts"
      });

      const artifactPath = String(output.artifact_path);
      expectArtifactPath(artifactPath, await realpath(join(root, "direct-artifacts")), "research");
      expect(await stat(join(artifactPath, "bundle-manifest.json"))).toBeDefined();
    } finally {
      process.chdir(previousCwd);
    }
  });

  it("marks empty shopping provider runs as env-limited failures instead of silent success", async () => {
    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const providerId = options?.providerIds?.[0] ?? "shopping/walmart";
        return makeAggregate({
          sourceSelection: "shopping",
          providerOrder: [providerId],
          records: [{
            id: `${providerId}-index`,
            source: "shopping",
            provider: providerId,
            url: "https://www.walmart.com/search?q=portable+monitor",
            title: "Walmart search: portable monitor",
            content: "$10",
            timestamp: "2026-02-16T00:00:00.000Z",
            confidence: 0.4,
            attributes: {
              retrievalPath: "shopping:search:index",
              shopping_offer: {
                provider: providerId,
                product_id: "index-row",
                title: "Walmart search: portable monitor",
                url: "https://www.walmart.com/search?q=portable+monitor",
                price: { amount: 10, currency: "USD", retrieved_at: "2026-02-16T00:00:00.000Z" },
                shipping: { amount: 0, currency: "USD", notes: "unknown" },
                availability: "unknown",
                rating: 0,
                reviews_count: 0
              }
            }
          }]
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runShoppingWorkflow(runtime, {
      query: "portable monitor",
      providers: ["shopping/walmart"],
      mode: "json"
    });

    expect(output.offers).toEqual([]);
    expect((output.meta as {
      failures: Array<{ provider: string; error: { reasonCode?: string; details?: { noOfferRecords?: boolean } } }>;
      metrics: {
        failed_providers: string[];
        reasonCodeDistribution: Record<string, number>;
      };
    })).toMatchObject({
      failures: [{
        provider: "shopping/walmart",
        error: {
          reasonCode: "env_limited",
          details: {
            noOfferRecords: true
          }
        }
      }],
      metrics: {
        failed_providers: ["shopping/walmart"],
        reasonCodeDistribution: {
          env_limited: 1
        }
      }
    });
    expect((output.meta as { metrics: Record<string, unknown> }).metrics).not.toHaveProperty("reason_code_distribution");
  });

  it("summarizes shopping challenge orchestration from successful browser-assisted records", async () => {
    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const providerId = options?.providerIds?.[0] ?? "shopping/target";
        return makeAggregate({
          sourceSelection: "shopping",
          providerOrder: [providerId],
          records: [{
            id: `${providerId}-offer`,
            source: "shopping",
            provider: providerId,
            url: "https://www.target.com/p/portable-monitor/-/A-123",
            title: "Portable monitor",
            content: "$129.99",
            timestamp: "2026-02-16T00:00:00.000Z",
            confidence: 0.8,
            attributes: {
              retrievalPath: "shopping:search:result-card",
              browser_fallback_mode: "extension",
              browser_fallback_reason_code: "challenge_detected",
              browser_fallback_challenge_orchestration: {
                mode: "browser_with_helper",
                source: "config",
                status: "resolved"
              },
              shopping_offer: {
                provider: providerId,
                product_id: "target-monitor",
                title: "Portable monitor",
                url: "https://www.target.com/p/portable-monitor/-/A-123",
                price: {
                  amount: 129.99,
                  currency: "USD",
                  retrieved_at: "2026-02-16T00:00:00.000Z"
                },
                shipping: {
                  amount: 0,
                  currency: "USD",
                  notes: "free"
                },
                availability: "in_stock",
                rating: 4.7,
                reviews_count: 42
              }
            }
          }]
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runShoppingWorkflow(runtime, {
      query: "portable monitor",
      providers: ["shopping/target"],
      mode: "json"
    });

    expect((output.meta as {
      metrics: {
        challenge_orchestration: Array<Record<string, unknown>>;
      };
    }).metrics.challenge_orchestration).toEqual([
      expect.objectContaining({
        provider: "shopping/target",
        browserFallbackMode: "extension",
        browserFallbackReasonCode: "challenge_detected",
        mode: "browser_with_helper",
        source: "config",
        status: "resolved"
      })
    ]);
  });

  it("summarizes shopping challenge orchestration from failure details when browser recovery stays blocked", async () => {
    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const providerId = options?.providerIds?.[0] ?? "shopping/temu";
        return makeAggregate({
          ok: false,
          sourceSelection: "shopping",
          providerOrder: [providerId],
          failures: [{
            provider: providerId,
            source: "shopping",
            error: {
              code: "unavailable",
              message: "challenge remains active",
              retryable: false,
              reasonCode: "challenge_detected",
              provider: providerId,
              source: "shopping",
              details: {
                browserFallbackMode: "extension",
                browserFallbackReasonCode: "challenge_detected",
                challengeOrchestration: {
                  mode: "browser_with_helper",
                  source: "config",
                  status: "deferred"
                }
              }
            }
          }]
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runShoppingWorkflow(runtime, {
      query: "wireless mouse",
      providers: ["shopping/temu"],
      mode: "json"
    });

    expect((output.meta as {
      metrics: {
        challenge_orchestration: Array<Record<string, unknown>>;
      };
    }).metrics.challenge_orchestration).toEqual([
      expect.objectContaining({
        provider: "shopping/temu",
        reasonCode: "challenge_detected",
        browserFallbackMode: "extension",
        browserFallbackReasonCode: "challenge_detected",
        mode: "browser_with_helper",
        source: "config",
        status: "deferred"
      })
    ]);
  });

  it("reports preserved extension challenge diagnostics unchanged for auto shopping mode", async () => {
    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const providerId = options?.providerIds?.[0] ?? "shopping/walmart";
        return makeAggregate({
          ok: false,
          sourceSelection: "shopping",
          providerOrder: [providerId],
          failures: [{
            provider: providerId,
            source: "shopping",
            error: {
              code: "unavailable",
              message: "challenge remains active",
              retryable: false,
              reasonCode: "challenge_detected",
              provider: providerId,
              source: "shopping",
              details: {
                disposition: "challenge_preserved",
                browserFallbackMode: "extension",
                browserFallbackReasonCode: "challenge_detected",
                preservedSessionId: "preserved-session-1",
                preservedTargetId: "tab-123",
                challengeOrchestration: {
                  mode: "browser_with_helper",
                  source: "config",
                  status: "deferred"
                }
              }
            }
          }],
          metrics: { attempted: 1, succeeded: 0, failed: 1, retries: 0, latencyMs: 1 }
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runShoppingWorkflow(runtime, {
      query: "macbook pro m4 32gb ram",
      providers: ["shopping/walmart"],
      browserMode: "auto",
      mode: "json"
    });

    expect((output.meta as {
      selection: { requested_browser_mode?: string };
      metrics: {
        browser_fallback_modes_observed: string[];
        challenge_orchestration: Array<Record<string, unknown>>;
      };
    })).toMatchObject({
      selection: {
        requested_browser_mode: "auto"
      },
      metrics: {
        browser_fallback_modes_observed: ["extension"],
        challenge_orchestration: [
          expect.objectContaining({
            provider: "shopping/walmart",
            reasonCode: "challenge_detected",
            browserFallbackMode: "extension",
            browserFallbackReasonCode: "challenge_detected",
            mode: "browser_with_helper",
            source: "config",
            status: "deferred"
          })
        ]
      }
    });
    expect((output.meta as {
      metrics: { browser_fallback_modes_observed: string[] };
    }).metrics.browser_fallback_modes_observed).not.toContain("managed_headed");
  });

  it("preserves auth-required blocker reasons when empty shopping runs are actually login pages", async () => {
    const runtime = toRuntime({
      search: vi.fn(async (_input, options) => {
        const providerId = options?.providerIds?.[0] ?? "shopping/temu";
        return makeAggregate({
          sourceSelection: "shopping",
          providerOrder: [providerId],
          records: [{
            id: `${providerId}-login`,
            source: "shopping",
            provider: providerId,
            url: "https://www.temu.com/login.html?from=https%3A%2F%2Fwww.temu.com%2Fsearch_result.html",
            title: "Temu | Login",
            content: "Please log in to continue shopping.",
            timestamp: "2026-02-16T00:00:00.000Z",
            confidence: 0.6,
            attributes: {
              retrievalPath: "shopping:search:index"
            }
          }]
        });
      }),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runShoppingWorkflow(runtime, {
      query: "wireless mouse",
      providers: ["shopping/temu"],
      mode: "json"
    });

    expect((output.meta as {
      failures: Array<{ provider: string; error: { reasonCode?: string; details?: { blockerType?: string; title?: string } } }>;
      primaryConstraint: {
        guidance: {
          reason: string;
          recommendedNextCommands: string[];
        };
      };
      metrics: {
        reasonCodeDistribution: Record<string, number>;
      };
    })).toMatchObject({
      failures: [{
        provider: "shopping/temu",
        error: {
          reasonCode: "token_required",
          details: {
            blockerType: "auth_required",
            title: "Temu | Login"
          }
        }
      }],
      primaryConstraint: {
        guidance: {
          reason: "Temu needs an authenticated session before retrying.",
          recommendedNextCommands: [
            "Reuse a user-authorized signed-in browser session, load cookies only from that authorized session, or use the provider sign-in flow.",
            "Rerun the same provider or workflow once the session is active."
          ]
        }
      },
      metrics: {
        reasonCodeDistribution: {
          token_required: 1
        }
      }
    });
    expect((output.meta as { metrics: Record<string, unknown> }).metrics).not.toHaveProperty("reason_code_distribution");
  });

  it("clears stale provider guidance when shopping offer filtering overrides the primary summary", async () => {
    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate({
        ok: false,
        sourceSelection: "shopping",
        providerOrder: ["shopping/amazon"],
        records: [{
          id: "offer-over-budget",
          source: "shopping",
          provider: "shopping/amazon",
          url: "https://example.com/offer-over-budget",
          title: "Expensive monitor",
          content: "$99.00",
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.9,
          attributes: {
            shopping_offer: {
              provider: "shopping/amazon",
              product_id: "offer-over-budget",
              title: "Expensive monitor",
              url: "https://example.com/offer-over-budget",
              price: {
                amount: 99,
                currency: "USD",
                retrieved_at: "2026-02-16T00:00:00.000Z"
              }
            }
          }
        }],
        failures: [{
          provider: "shopping/amazon",
          source: "shopping",
          error: {
            code: "unavailable",
            message: "provider follow-up required",
            retryable: false,
            reasonCode: "env_limited",
            provider: "shopping/amazon",
            source: "shopping",
            details: {
              reasonCode: "env_limited"
            }
          }
        }]
      })),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runShoppingWorkflow(runtime, {
      query: "portable monitor",
      providers: ["shopping/amazon"],
      budget: 10,
      mode: "json"
    });

    expect((output.meta as {
      primaryConstraintSummary: string;
      primaryConstraint: Record<string, unknown>;
    })).toMatchObject({
      primaryConstraintSummary: "All candidate offers exceeded the requested budget of 10.00.",
      primaryConstraint: {
        summary: "All candidate offers exceeded the requested budget of 10.00.",
        guidance: {
          reason: "Amazon needs a live browser-rendered page before retrying."
        }
      }
    });
  });

  it("filters search-index and asset rows out of shopping offers", async () => {
    const now = "2026-02-16T00:00:00.000Z";
    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate({
        sourceSelection: "shopping",
        providerOrder: ["shopping/amazon"],
        records: [
          {
            id: "amazon-index",
            source: "shopping",
            provider: "shopping/amazon",
            url: "https://www.amazon.com/s?k=portable+monitor",
            title: "Amazon search: portable monitor",
            content: "$10",
            timestamp: now,
            confidence: 0.5,
            attributes: {
              retrievalPath: "shopping:search:index",
              shopping_offer: {
                provider: "shopping/amazon",
                product_id: "index",
                title: "Amazon search: portable monitor",
                url: "https://www.amazon.com/s?k=portable+monitor",
                price: { amount: 10, currency: "USD", retrieved_at: now },
                shipping: { amount: 0, currency: "USD", notes: "unknown" },
                availability: "limited",
                rating: 0,
                reviews_count: 0
              }
            }
          },
          {
            id: "amazon-asset",
            source: "shopping",
            provider: "shopping/amazon",
            url: "https://images-na.ssl-images-amazon.com/logo.png",
            title: "https://images-na.ssl-images-amazon.com/logo.png",
            content: "$10",
            timestamp: now,
            confidence: 0.4,
            attributes: {
              retrievalPath: "shopping:search:link",
              shopping_offer: {
                provider: "shopping/amazon",
                product_id: "asset",
                title: "https://images-na.ssl-images-amazon.com/logo.png",
                url: "https://images-na.ssl-images-amazon.com/logo.png",
                price: { amount: 10, currency: "USD", retrieved_at: now },
                shipping: { amount: 0, currency: "USD", notes: "unknown" },
                availability: "limited",
                rating: 0,
                reviews_count: 0
              }
            }
          },
          {
            id: "amazon-cdn-card",
            source: "shopping",
            provider: "shopping/amazon",
            url: "https://m.media-amazon.com/images/I/portable-monitor.jpg",
            title: "UPERFECT Portable Monitor 15.6 inch USB-C Travel Display",
            content: "$69.99",
            timestamp: now,
            confidence: 0.8,
            attributes: {
              retrievalPath: "shopping:search:result-card",
              shopping_offer: {
                provider: "shopping/amazon",
                product_id: "cdn-card",
                title: "UPERFECT Portable Monitor 15.6 inch USB-C Travel Display",
                url: "https://m.media-amazon.com/images/I/portable-monitor.jpg",
                price: { amount: 69.99, currency: "USD", retrieved_at: now },
                shipping: { amount: 0, currency: "USD", notes: "free" },
                availability: "in_stock",
                rating: 4.8,
                reviews_count: 29
              }
            }
          },
          {
            id: "amazon-valid-card",
            source: "shopping",
            provider: "shopping/amazon",
            url: "https://www.amazon.com/dp/B0TEST1234",
            title: "UPERFECT Portable Monitor 15.6 inch USB-C Travel Display",
            content: "$69.99",
            timestamp: now,
            confidence: 0.9,
            attributes: {
              retrievalPath: "shopping:search:result-card",
              shopping_offer: {
                provider: "shopping/amazon",
                product_id: "valid-card",
                title: "UPERFECT Portable Monitor 15.6 inch USB-C Travel Display",
                url: "https://www.amazon.com/dp/B0TEST1234",
                price: { amount: 69.99, currency: "USD", retrieved_at: now },
                shipping: { amount: 0, currency: "USD", notes: "free" },
                availability: "in_stock",
                rating: 4.8,
                reviews_count: 29
              }
            }
          }
        ]
      })),
      fetch: vi.fn(async () => makeAggregate())
    });

    const output = await runShoppingWorkflow(runtime, {
      query: "portable monitor",
      providers: ["shopping/amazon"],
      sort: "lowest_price",
      mode: "json"
    });

    expect(output.offers).toMatchObject([
      {
        provider: "shopping/amazon",
        url: "https://www.amazon.com/dp/B0TEST1234",
        title: "UPERFECT Portable Monitor 15.6 inch USB-C Travel Display"
      }
    ]);
  });

  it("blocks shopping workflows when legal review approval is expired", async () => {
    const search = vi.fn(async () => makeAggregate());
    const runtime = toRuntime({
      search,
      fetch: vi.fn(async () => makeAggregate())
    });

    vi.useFakeTimers();
    vi.setSystemTime(new Date("2031-01-01T00:00:00.000Z"));

    await expect(runShoppingWorkflow(runtime, {
      query: "usb hub",
      mode: "json"
    })).rejects.toThrow("Provider legal review checklist invalid or expired");

    expect(search).not.toHaveBeenCalled();
    vi.useRealTimers();
  });

  it("builds product-video assets for URL and name flows", async () => {
    const runtime = toRuntime({
      search: vi.fn(async (input, options) => {
        const providerId = options?.providerIds?.[0] ?? "shopping/amazon";
        return makeAggregate({
          sourceSelection: "shopping",
          providerOrder: [providerId],
          records: [{
            id: "offer-1",
            source: "shopping",
            provider: providerId,
            url: "https://example.com/product/1",
            title: String(input.query),
            content: "$19.99",
            timestamp: "2026-02-16T00:00:00.000Z",
            confidence: 0.8,
            attributes: {
              shopping_offer: {
                provider: providerId,
                product_id: "p-1",
                title: "Sample Product",
                url: "https://example.com/product/1",
                price: { amount: 19.99, currency: "USD", retrieved_at: "2026-02-16T00:00:00.000Z" },
                shipping: { amount: 0, currency: "USD", notes: "free" },
                availability: "in_stock",
                rating: 4.5,
                reviews_count: 11
              }
            }
          }]
        });
      }),
      fetch: vi.fn(async () => makeAggregate({
        sourceSelection: "shopping",
        providerOrder: ["shopping/amazon"],
        records: [{
          id: "product-1-marketplace-shell",
          source: "shopping",
          provider: "shopping/amazon",
          url: "https://example.com/product/1",
          title: "Quantity 1 available Buy It Now",
          content: [
            "Quantity 1 available.",
            "Condition: New: A brand-new, unused, unopened, undamaged item in its original packaging.",
            "May not ship to Canada.",
            "Seller feedback is 98% positive.",
            "Buy It Now and add to cart."
          ].join(" "),
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.5,
          attributes: {
            links: [],
            shopping_offer: {
              provider: "shopping/amazon",
              product_id: "p-1",
              title: "Quantity 1 available Buy It Now",
              url: "https://example.com/product/1",
              price: { amount: 19.99, currency: "USD", retrieved_at: "2026-02-16T00:00:00.000Z" },
              shipping: { amount: 0, currency: "USD", notes: "free" },
              availability: "in_stock",
              rating: 4.5,
              reviews_count: 11
            }
          }
        }, {
          id: "product-1",
          source: "shopping",
          provider: "shopping/amazon",
          url: "https://example.com/product/1",
          title: "Sample Product",
          content: "Type Vertical Mouse Maximum DPI 1200 Connectivity Wireless Features Adjustable DPI, Ergonomic.",
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.9,
          attributes: {
            links: ["https://cdn.example.com/image-1.jpg"],
            headers: {
              authorization: "Bearer secret-token-123"
            },
            api_token: "sk_test_123",
            shopping_offer: {
              provider: "shopping/amazon",
              product_id: "p-1",
              title: "Sample Product",
              url: "https://example.com/product/1",
              price: { amount: 19.99, currency: "USD", retrieved_at: "2026-02-16T00:00:00.000Z" },
              shipping: { amount: 0, currency: "USD", notes: "free" },
              availability: "in_stock",
              rating: 4.5,
              reviews_count: 11
            }
          }
        }]
      }))
    });

    vi.stubGlobal("fetch", vi.fn(async () => ({
      ok: true,
      arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer
    })) as unknown as typeof fetch);

    const outputByUrl = await runProductVideoWorkflow(runtime, {
      product_url: "https://example.com/product/1",
      include_screenshots: true,
      include_all_images: true,
      include_copy: true
    }, {
      captureScreenshot: async () => Buffer.from([9, 8, 7])
    });

    expect(outputByUrl).toMatchObject({
      artifact_path: expect.any(String),
      manifest: {
        source_url: "https://example.com/product/1"
      }
    });

    const outputByUrlPath = String(outputByUrl.artifact_path);
    const outputByUrlFiles = await readdir(outputByUrlPath);
    expect(outputByUrlFiles).toEqual(expect.arrayContaining([
      "manifest.json",
      "product.json",
      "pricing.json",
      "copy.md",
      "features.md",
      "presentation-readiness.json",
      "bundle-manifest.json"
    ]));
    const manifestRaw = await readFile(join(outputByUrlPath, "manifest.json"), "utf8");
    const manifest = JSON.parse(manifestRaw) as {
      source_url: string;
      readiness?: { presentation?: { status?: string }; productVideo?: { status?: string } };
    };
    expect(manifest).toMatchObject({
      source_url: "https://example.com/product/1",
      readiness: {
        presentation: { status: "pass" },
        productVideo: { status: "pass" }
      }
    });
    const productRaw = await readFile(join(outputByUrlPath, "product.json"), "utf8");
    const product = JSON.parse(productRaw) as {
      presentationReadiness?: { status?: string; reasonCodes?: string[] };
      productVideoReadiness?: { status?: string; reasonCodes?: string[] };
      features?: string[];
      copy?: string;
    };
    expect(product.presentationReadiness).toMatchObject({
      status: "pass",
      reasonCodes: expect.arrayContaining(["selected_record_changed", "positive_spec_promoted"])
    });
    expect(product.productVideoReadiness).toMatchObject({
      status: "pass",
      reasonCodes: expect.arrayContaining(["selected_record_changed", "positive_spec_promoted"])
    });
    expect(product.features?.join("\n")).toMatch(/Vertical Mouse/i);
    expect(product.copy).toMatch(/presentation highlights verified product details/i);

    const readinessRaw = await readFile(join(outputByUrlPath, "presentation-readiness.json"), "utf8");
    const readiness = JSON.parse(readinessRaw) as {
      selectedRecordId?: string;
      originalPrimaryRecordId?: string;
      candidateSummaries?: Array<{
        recordId?: string;
        title?: string;
        cleanSpecCount?: number;
        rejectedCandidateCount?: number;
      }>;
      summary?: { status?: string; promotedFeatureCount?: number; rejectedCandidateCount?: number };
    };
    expect(readiness).toMatchObject({
      selectedRecordId: "product-1",
      originalPrimaryRecordId: "product-1-marketplace-shell",
      summary: {
        status: "pass"
      }
    });
    expect(readiness.summary?.promotedFeatureCount).toBeGreaterThanOrEqual(4);
    expect(readiness.candidateSummaries).toEqual(expect.arrayContaining([
      expect.objectContaining({
        recordId: "product-1-marketplace-shell",
        cleanSpecCount: 0
      })
    ]));
    expect(readiness.candidateSummaries?.some((summary) => (
      /Quantity 1 available Buy It Now/i.test(summary.title ?? "")
    ))).toBe(false);
    const selectedCandidateSummary = readiness.candidateSummaries?.find((summary) => summary.recordId === "product-1");
    expect(selectedCandidateSummary?.cleanSpecCount).toBeGreaterThanOrEqual(4);

    const rawSourceRaw = await readFile(join(outputByUrlPath, "raw/source-record.json"), "utf8");
    const rawSource = JSON.parse(rawSourceRaw) as {
      id?: string;
      attributes?: {
        headers?: { authorization?: string };
        api_token?: string;
      };
    };
    expect(rawSource.id).toBe("product-1");
    expect(rawSource.attributes?.headers?.authorization).toBe("[REDACTED]");
    expect(rawSource.attributes?.api_token).toBe("[REDACTED]");

    const outputByName = await runProductVideoWorkflow(runtime, {
      product_name: "sample product",
      provider_hint: "shopping/amazon",
      include_screenshots: false
    });

    expect(outputByName).toMatchObject({
      artifact_path: expect.any(String)
    });

    await expect(runProductVideoWorkflow(runtime, {})).rejects.toThrow("product_url or product_name is required");
  });

  it("persists bounded product-video readiness summaries for noisy marketplace records", async () => {
    const outputRoot = await makeWorkspaceDir("odb-product-noisy-");
    const repeatedChromeFragments = 40;
    const longMarketplaceText = [
      "Quantity 1 available",
      "Condition: New: A brand-new unused unopened item",
      Array.from({ length: repeatedChromeFragments }, () => "Seller feedback stays visible in the raw capture").join(" "),
      "May not ship to Canada",
      "Buy It Now and checkout today",
      "Returns accepted within 30 days"
    ].join(" ");
    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate()),
      fetch: vi.fn(async () => makeAggregate({
        sourceSelection: "shopping",
        providerOrder: ["shopping/ebay"],
        records: [{
          id: "noisy-selected-record",
          source: "shopping",
          provider: "shopping/ebay",
          url: "https://www.ebay.com/itm/456",
          title: "Marketplace Noise Mouse",
          content: longMarketplaceText,
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.8,
          attributes: {
            links: [],
            shopping_offer: {
              provider: "shopping/ebay",
              product_id: "456",
              title: "Marketplace Noise Mouse",
              url: "https://www.ebay.com/itm/456",
              price: { amount: 39.99, currency: "USD", retrieved_at: "2026-02-16T00:00:00.000Z" },
              shipping: { amount: 0, currency: "USD", notes: "unknown" },
              availability: "in_stock",
              rating: 4.3,
              reviews_count: 7
            }
          }
        }]
      }))
    });

    const output = await runProductVideoWorkflow(runtime, {
      product_url: "https://www.ebay.com/itm/456",
      output_dir: outputRoot,
      include_screenshots: false,
      include_all_images: false,
      include_copy: true
    });
    const outputPath = String(output.artifact_path);
    const copyMarkdown = await readFile(join(outputPath, "copy.md"), "utf8");
    const featuresMarkdown = await readFile(join(outputPath, "features.md"), "utf8");
    const readinessRaw = await readFile(join(outputPath, "presentation-readiness.json"), "utf8");
    const rawSourceRaw = await readFile(join(outputPath, "raw/source-record.json"), "utf8");
    const readiness = JSON.parse(readinessRaw) as {
      rejectedCandidates?: Array<{
        candidateHash?: string;
        candidateExcerpt?: string;
        evidenceReferenceCount?: number;
        evidenceReferences?: Array<{ excerpt?: string; path?: string }>;
      }>;
      summary?: { status?: string };
    };
    const publicMarkdown = [copyMarkdown, featuresMarkdown].join("\n");

    expect(publicMarkdown).not.toMatch(/Seller feedback/i);
    expect(publicMarkdown).not.toMatch(/Buy It Now/i);
    expect(publicMarkdown).not.toMatch(/Returns accepted/i);
    expect(readiness.summary?.status).toBe("fail");
    expect(readinessRaw).not.toContain(longMarketplaceText);
    expect(readinessRaw).not.toMatch(/Seller feedback|Buy It Now|Returns accepted/i);
    expect(readiness.rejectedCandidates?.[0]?.candidateHash).toMatch(/^[a-f0-9]{16}$/);
    expect(readiness.rejectedCandidates?.[0]?.candidateExcerpt).toBeUndefined();
    expect(readiness.rejectedCandidates?.[0]?.evidenceReferenceCount).toBeGreaterThan(0);
    expect(readiness.rejectedCandidates?.[0]?.evidenceReferences?.some((reference) => (
      "excerpt" in reference
    ))).toBe(false);
    expect(rawSourceRaw).toContain(longMarketplaceText);
  });

  it("bounds product-video readiness summaries for clean promoted claims and candidate titles", async () => {
    const outputRoot = await makeWorkspaceDir("odb-product-bounded-clean-");
    const repeatedTitleParts = 30;
    const repeatedFeatureParts = 30;
    const maxSummaryExcerptLength = 180;
    const longCandidateTitle = [
      "Clean Ergonomic Mouse",
      ...Array.from({ length: repeatedTitleParts }, () => "long evidence title segment")
    ].join(" ");
    const longFeatureValue = Array.from({ length: repeatedFeatureParts }, () => "adaptive grip control").join(" ");
    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate()),
      fetch: vi.fn(async () => makeAggregate({
        sourceSelection: "shopping",
        providerOrder: ["shopping/amazon"],
        records: [{
          id: "bounded-clean-record",
          source: "shopping",
          provider: "shopping/amazon",
          url: "https://example.com/product/bounded",
          title: longCandidateTitle,
          content: [
            "Type Vertical Mouse.",
            "Maximum DPI 1200.",
            "Connectivity Wireless.",
            `Features ${longFeatureValue}.`
          ].join(" "),
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.9,
          attributes: {
            links: [],
            shopping_offer: {
              provider: "shopping/amazon",
              product_id: "bounded-clean-record",
              title: longCandidateTitle,
              url: "https://example.com/product/bounded",
              price: { amount: 59.99, currency: "USD", retrieved_at: "2026-02-16T00:00:00.000Z" },
              shipping: { amount: 0, currency: "USD", notes: "free" },
              availability: "in_stock",
              rating: 4.6,
              reviews_count: 21
            }
          }
        }]
      }))
    });

    const output = await runProductVideoWorkflow(runtime, {
      product_url: "https://example.com/product/bounded",
      output_dir: outputRoot,
      include_screenshots: false,
      include_all_images: false,
      include_copy: true
    });
    const outputPath = String(output.artifact_path);
    const readinessRaw = await readFile(join(outputPath, "presentation-readiness.json"), "utf8");
    const rawSourceRaw = await readFile(join(outputPath, "raw/source-record.json"), "utf8");
    const readiness = JSON.parse(readinessRaw) as {
      candidateSummaries?: Array<{ title?: string }>;
      promotedClaims?: Array<{
        claim?: string;
        claimHash?: string;
        claimLength?: number;
        specValue?: string;
        specValueHash?: string;
        specValueLength?: number;
      }>;
    };

    expect(readinessRaw).not.toContain(longCandidateTitle);
    expect(readinessRaw).not.toContain(longFeatureValue);
    expect(rawSourceRaw).toContain(longCandidateTitle);
    expect(rawSourceRaw).toContain(longFeatureValue);
    expect(readiness.candidateSummaries?.every((summary) => (
      (summary.title?.length ?? 0) <= maxSummaryExcerptLength
    ))).toBe(true);
    expect(readiness.promotedClaims?.every((claim) => (
      (claim.claim?.length ?? 0) <= maxSummaryExcerptLength
      && (claim.specValue?.length ?? 0) <= maxSummaryExcerptLength
    ))).toBe(true);
    const expectedFeatureClaim = `${longFeatureValue.charAt(0).toUpperCase()}${longFeatureValue.slice(1)}.`;
    const boundedFeatureClaim = readiness.promotedClaims?.find((claim) => claim.specValue?.endsWith("…"));
    expect(boundedFeatureClaim).toEqual(expect.objectContaining({
      claimHash: summaryHash(expectedFeatureClaim),
      claimLength: expectedFeatureClaim.length,
      specValueHash: summaryHash(longFeatureValue),
      specValueLength: longFeatureValue.length
    }));
  });

  it("stores default product-video artifacts under the workspace product-video directory", async () => {
    const workspaceDir = await makeWorkspaceDir("odb-product-workspace-");
    const cwdSpy = vi.spyOn(process, "cwd").mockReturnValue(workspaceDir);

    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate()),
      fetch: vi.fn(async (_input, options) => {
        expect(options?.suspendedIntent).toMatchObject({
          kind: "workflow.product_video",
          input: {
            workflow: {
              kind: "product_video",
              input: { output_dir: join(workspaceDir, ".opendevbrowser") }
            }
          }
        });
        return makeAggregate({
        sourceSelection: "shopping",
        providerOrder: ["shopping/amazon"],
        records: [{
          id: "product-artifact",
          source: "shopping",
          provider: "shopping/amazon",
          url: "https://example.com/product-artifact",
          title: "Workspace Product",
          content: "Feature one. Feature two. Feature three.",
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.9,
          attributes: {
            shopping_offer: {
              provider: "shopping/amazon",
              product_id: "product-artifact",
              title: "Workspace Product",
              url: "https://example.com/product-artifact",
              price: {
                amount: 49,
                currency: "USD",
                retrieved_at: "2026-02-16T00:00:00.000Z"
              },
              availability: "in_stock"
            }
          }
        }]
        });
      })
    });

    try {
      const output = await runProductVideoWorkflow(runtime, {
        product_url: "https://example.com/product-artifact"
      });

      const artifactPath = String(output.artifact_path);
      expectArtifactPath(artifactPath, join(workspaceDir, ".opendevbrowser"), "product-video");
      const manifest = JSON.parse(
        await readFile(join(artifactPath, "bundle-manifest.json"), "utf8")
      ) as ArtifactManifest;
      expect(manifest.files).toContain("manifest.json");
    } finally {
      cwdSpy.mockRestore();
    }
  });

  it("stores product-video name-resolution artifacts under the requested root", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-product-nested-"));
    createdDirs.push(root);
    const productUrl = "https://example.com/product-artifact";

    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate({
        sourceSelection: "shopping",
        providerOrder: ["shopping/amazon"],
        records: [{
          id: "resolved-offer",
          source: "shopping",
          provider: "shopping/amazon",
          url: productUrl,
          title: "Workspace Product",
          content: "$49",
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.9,
          attributes: {
            shopping_offer: {
              provider: "shopping/amazon",
              product_id: "resolved-offer",
              title: "Workspace Product",
              url: productUrl,
              price: {
                amount: 49,
                currency: "USD",
                retrieved_at: "2026-02-16T00:00:00.000Z"
              },
              availability: "in_stock"
            }
          }
        }]
      })),
      fetch: vi.fn(async () => makeAggregate({
        sourceSelection: "shopping",
        providerOrder: ["shopping/amazon"],
        records: [{
          id: "product-artifact",
          source: "shopping",
          provider: "shopping/amazon",
          url: productUrl,
          title: "Workspace Product",
          content: "Feature one. Feature two. Feature three.",
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.9,
          attributes: {
            shopping_offer: {
              provider: "shopping/amazon",
              product_id: "product-artifact",
              title: "Workspace Product",
              url: productUrl,
              price: {
                amount: 49,
                currency: "USD",
                retrieved_at: "2026-02-16T00:00:00.000Z"
              },
              availability: "in_stock"
            }
          }
        }]
      }))
    });

    const output = await runProductVideoWorkflow(runtime, {
      product_name: "Workspace Product",
      provider_hint: "shopping/amazon",
      output_dir: root,
      ttl_hours: 12
    });

    const shoppingRuns = await readdir(join(root, "shopping"));
    expect(shoppingRuns).toHaveLength(1);
    const productVideoPath = String(output.artifact_path);
    const shoppingPath = join(root, "shopping", shoppingRuns[0] ?? "");
    expectArtifactPath(productVideoPath, root, "product-video");
    expectArtifactPath(shoppingPath, root, "shopping");
    const shoppingManifest = JSON.parse(
      await readFile(join(shoppingPath, "bundle-manifest.json"), "utf8")
    ) as ArtifactManifest;
    const productManifest = JSON.parse(
      await readFile(join(productVideoPath, "bundle-manifest.json"), "utf8")
    ) as ArtifactManifest;
    expect(shoppingManifest.ttl_hours).toBe(12);
    expect(productManifest.ttl_hours).toBe(12);
  });

  it("rejects invalid product targets before creating a product-video artifact bundle", async () => {
    const root = await mkdtemp(join(tmpdir(), "odb-product-invalid-"));
    createdDirs.push(root);

    const auxiliaryFetch = vi.fn(async () => ({
      ok: true,
      url: "https://www.shoott.com/headshots",
      text: async () => "<html></html>",
      arrayBuffer: async () => new Uint8Array([1, 2, 3]).buffer
    }));
    vi.stubGlobal("fetch", auxiliaryFetch as unknown as typeof fetch);

    const runtime = toRuntime({
      search: vi.fn(async () => makeAggregate()),
      fetch: vi.fn(async () => makeAggregate({
        sourceSelection: "web",
        providerOrder: ["web/default"],
        records: [{
          id: "shoott-404",
          source: "web",
          provider: "web/default",
          url: "https://www.shoott.com/headshots",
          title: "Shoott | 404",
          content: "Error 404 We can’t seem to find the page you were looking for.",
          timestamp: "2026-02-16T00:00:00.000Z",
          confidence: 0.8,
          attributes: {
            status: 404,
            links: ["https://cdn.example.com/404-image.jpg"]
          }
        }]
      }))
    });

    await expect(runProductVideoWorkflow(runtime, {
      product_url: "https://www.shoott.com/headshots",
      output_dir: root,
      include_screenshots: true,
      include_all_images: true,
      include_copy: true
    })).rejects.toThrow("Product target appears to be a not-found page");

    await expect(stat(join(root, "product-video"))).rejects.toThrow();
    expect(auxiliaryFetch).not.toHaveBeenCalled();
  });
});
