import {
  confidenceInterval
} from "./chunk-PJQFMIOX.js";
import {
  CostAccountingIncompleteError,
  CostLedger
} from "./chunk-ZMXDQ4K7.js";
import {
  AgentEvalError,
  ValidationError
} from "./chunk-ONWEPEDO.js";

// src/campaign/run-campaign.ts
import { join as join3 } from "path";

// src/integrity/backend-integrity.ts
var BackendIntegrityError = class extends AgentEvalError {
  constructor(message, report) {
    super("backend_integrity", message);
    this.report = report;
  }
  report;
};
function summarizeBackendIntegrity(records) {
  return summarizeBackendUsage(
    records.map((record) => ({
      inputTokens: record.tokenUsage.input,
      outputTokens: record.tokenUsage.output,
      costUsd: record.costUsd
    }))
  );
}
function summarizeAgentReceiptIntegrity(receipts) {
  return summarizeBackendUsage(
    receipts.filter((receipt) => receipt.channel === "agent").map((receipt) => ({
      inputTokens: receipt.inputTokens,
      outputTokens: receipt.outputTokens,
      costUsd: receipt.costUsd
    }))
  );
}
function summarizeBackendUsage(records) {
  const totalRecords = records.length;
  let stubRecords = 0;
  let realRecords = 0;
  let uncostedRecords = 0;
  let totalInputTokens = 0;
  let totalOutputTokens = 0;
  let totalCostUsd = 0;
  for (const rec of records) {
    totalInputTokens += rec.inputTokens;
    totalOutputTokens += rec.outputTokens;
    totalCostUsd += rec.costUsd;
    if (rec.inputTokens === 0 && rec.outputTokens === 0) stubRecords++;
    else realRecords++;
    if (rec.outputTokens > 0 && rec.costUsd === 0) uncostedRecords++;
  }
  const verdict = totalRecords === 0 ? "stub" : stubRecords === totalRecords ? "stub" : stubRecords === 0 ? "real" : "mixed";
  const diagnosis = buildDiagnosis({
    totalRecords,
    stubRecords,
    realRecords,
    uncostedRecords,
    totalInputTokens,
    totalOutputTokens,
    totalCostUsd,
    verdict
  });
  return {
    totalRecords,
    stubRecords,
    realRecords,
    uncostedRecords,
    totalInputTokens,
    totalOutputTokens,
    totalCostUsd,
    verdict,
    diagnosis
  };
}
function buildDiagnosis(r) {
  if (r.totalRecords === 0) {
    return "no records \u2014 eval produced zero runs; backend likely failed before first turn";
  }
  if (r.verdict === "stub") {
    return [
      `all ${r.totalRecords} records have zero token usage \u2014 the LLM backend was never called.`,
      "common causes: --backend sandbox without a sandbox bridge running; stub model returning hard-coded strings;",
      "auth misconfigured so requests were silently dropped before the LLM. Re-run with --backend tcloud and TANGLE_API_KEY set,",
      "or boot the cli-bridge / sandbox before invoking the eval."
    ].join(" ");
  }
  if (r.verdict === "mixed") {
    const pct = (r.stubRecords / r.totalRecords * 100).toFixed(0);
    return [
      `${r.stubRecords}/${r.totalRecords} records (${pct}%) have zero token usage \u2014 the backend partially failed.`,
      "common causes: rate-limit cascade (429s after the first N personas);",
      "transient auth expiry mid-run; provider outage. Treat the affected records as missing data, not agent failures."
    ].join(" ");
  }
  if (r.uncostedRecords > 0) {
    const pct = (r.uncostedRecords / r.totalRecords * 100).toFixed(0);
    return [
      `${r.totalRecords} records with real LLM activity (in=${r.totalInputTokens}, out=${r.totalOutputTokens} tokens).`,
      `${r.uncostedRecords} (${pct}%) have output tokens but costUsd=0. Two distinct roots:`,
      "(a) cost ledger mis-wired \u2014 no usage propagation from the runtime stream into RunRecord; or",
      "(b) the model is unpriced at the source (sandbox/router returned $0 despite real tokens).",
      "For (b), price the measured tokens against the substrate table (estimateCost) instead of leaving $0."
    ].join(" ");
  }
  return `${r.totalRecords} records with real LLM activity (in=${r.totalInputTokens}, out=${r.totalOutputTokens} tokens, $${r.totalCostUsd.toFixed(4)}).`;
}
function assertRealBackend(records, opts = {}) {
  const report = summarizeBackendIntegrity(records);
  return assertBackendReport(report, opts);
}
function assertRealAgentReceipts(receipts, opts = {}) {
  const report = summarizeAgentReceiptIntegrity(receipts);
  return assertBackendReport(report, opts);
}
function assertBackendReport(report, opts) {
  const allowMixed = opts.allowMixed ?? true;
  if (report.verdict === "stub") {
    throw new BackendIntegrityError(
      `backend-integrity: ran against a stub or unconfigured backend \u2014 ${report.diagnosis}`,
      report
    );
  }
  if (!allowMixed && report.verdict === "mixed") {
    throw new BackendIntegrityError(
      `backend-integrity: partial backend failure rejected \u2014 ${report.diagnosis}`,
      report
    );
  }
  return report;
}

// src/verdict-cache.ts
import { createHash } from "crypto";
import { appendFileSync, existsSync, readFileSync } from "fs";
function canonicalizeAt(value, path) {
  if (value === null) return "null";
  switch (typeof value) {
    case "boolean":
      return value ? "true" : "false";
    case "number":
      if (!Number.isFinite(value)) {
        throw new Error(
          `canonicalJson: non-finite number (${value}) at ${path} \u2014 ambiguity is an error, not a coercion`
        );
      }
      return JSON.stringify(value);
    case "string":
      return JSON.stringify(value);
    case "undefined":
    case "function":
    case "symbol":
      throw new Error(
        `canonicalJson: ${typeof value} at ${path} \u2014 ambiguity is an error, not a coercion`
      );
    case "bigint":
      throw new Error(`canonicalJson: bigint at ${path} \u2014 not representable in JSON`);
    case "object":
      break;
  }
  const obj = value;
  if (typeof obj.toJSON === "function") {
    return canonicalizeAt(obj.toJSON(), path);
  }
  if (Array.isArray(obj)) {
    return `[${obj.map((item, i) => canonicalizeAt(item, `${path}[${i}]`)).join(",")}]`;
  }
  if (obj instanceof Map || obj instanceof Set) {
    throw new Error(
      `canonicalJson: ${obj instanceof Map ? "Map" : "Set"} at ${path} \u2014 would serialize as '{}'; convert to a plain object/array first`
    );
  }
  const keys = Object.keys(obj).sort();
  const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalizeAt(obj[k], `${path}.${k}`)}`);
  return `{${parts.join(",")}}`;
}
function canonicalJson(value) {
  return canonicalizeAt(value, "$");
}
function contentHash(value) {
  return createHash("sha256").update(canonicalJson(value)).digest("hex");
}
function inMemoryVerdictCache() {
  const entries = /* @__PURE__ */ new Map();
  return {
    get: (key) => entries.get(key),
    set: (key, score) => {
      entries.set(key, score);
    }
  };
}
function parseCacheLine(line, path, lineNo) {
  let parsed;
  try {
    parsed = JSON.parse(line);
  } catch (err) {
    throw new Error(
      `fileVerdictCache: corrupt JSONL at ${path}:${lineNo} \u2014 ${err instanceof Error ? err.message : String(err)}`
    );
  }
  const rec = parsed;
  if (typeof rec !== "object" || rec === null || typeof rec.key !== "string" || typeof rec.score !== "object" || rec.score === null || typeof rec.score.composite !== "number" || typeof rec.score.dimensions !== "object") {
    throw new Error(
      `fileVerdictCache: invalid record shape at ${path}:${lineNo} \u2014 expected {key, score:{dimensions, composite, notes}}`
    );
  }
  return rec;
}
function fileVerdictCache(path) {
  const entries = /* @__PURE__ */ new Map();
  if (existsSync(path)) {
    const lines = readFileSync(path, "utf8").split("\n");
    for (let i = 0; i < lines.length; i++) {
      const line = lines[i];
      if (line === void 0 || line.trim() === "") continue;
      const rec = parseCacheLine(line, path, i + 1);
      entries.set(rec.key, rec.score);
    }
  }
  return {
    get: (key) => entries.get(key),
    set: (key, score) => {
      appendFileSync(path, `${JSON.stringify({ key, score })}
`, "utf8");
      entries.set(key, score);
    }
  };
}
function cachedJudge(judge, store, options) {
  if (typeof options.judgeVersion !== "string" || options.judgeVersion.trim() === "") {
    throw new Error("cachedJudge: judgeVersion is required and must be a non-empty string");
  }
  const stats = { hits: 0, misses: 0 };
  const wrapped = {
    name: judge.name,
    dimensions: judge.dimensions,
    judgeVersion: options.judgeVersion,
    async score(input) {
      const key = contentHash({
        artifact: canonicalJson(input.artifact),
        scenarioId: input.scenario.id,
        judgeName: judge.name,
        dimensions: judge.dimensions,
        judgeVersion: options.judgeVersion
      });
      const cached = await store.get(key);
      if (cached !== void 0) {
        stats.hits += 1;
        return cached;
      }
      const score = await judge.score(input);
      await store.set(key, score);
      stats.misses += 1;
      return score;
    },
    stats: () => ({ ...stats })
  };
  if (judge.appliesTo) wrapped.appliesTo = judge.appliesTo;
  return wrapped;
}

// src/campaign/coverage.ts
function assertCampaignDesign(scenarios, reps) {
  if (!Number.isSafeInteger(reps) || reps < 1) {
    throw new Error("campaign design requires reps to be a positive safe integer");
  }
  const scenarioIds = /* @__PURE__ */ new Set();
  for (const scenario of scenarios) {
    if (typeof scenario.id !== "string" || scenario.id.trim().length === 0) {
      throw new Error("campaign design requires every scenario to have a non-empty id");
    }
    if (scenarioIds.has(scenario.id)) {
      throw new Error(`campaign design contains duplicate scenario id '${scenario.id}'`);
    }
    if (typeof scenario.kind !== "string" || scenario.kind.trim().length === 0) {
      throw new Error("campaign design requires every scenario to have a non-empty kind");
    }
    scenarioIds.add(scenario.id);
  }
}
function campaignScenarioIdentity(scenario) {
  assertCampaignDesign([scenario], 1);
  return {
    id: scenario.id,
    kind: scenario.kind,
    scenarioDigest: `sha256:${contentHash(scenario)}`
  };
}
function campaignSplitDigestFromIdentities(scenarios, reps) {
  assertCampaignDesign(scenarios, reps);
  for (const scenario of scenarios) {
    if (!/^sha256:[a-f0-9]{64}$/.test(scenario.scenarioDigest)) {
      throw new Error(`campaign scenario '${scenario.id}' has an invalid digest`);
    }
  }
  return `sha256:${contentHash({
    schema: "tangle.campaign-split",
    scenarios: scenarios.map(({ id, kind, scenarioDigest }) => ({ id, kind, scenarioDigest })),
    reps
  })}`;
}
function campaignSplitDigest(scenarios, reps) {
  assertCampaignDesign(scenarios, reps);
  return campaignSplitDigestFromIdentities(scenarios.map(campaignScenarioIdentity), reps);
}
function assertCampaignSplitIdentity(scenarios, reps, splitDigest) {
  if (campaignSplitDigestFromIdentities(scenarios, reps) !== splitDigest) {
    throw new Error("campaign split digest does not match its retained scenario identities");
  }
}
function campaignCoverage(cells, scenarios, reps, requireJudgeScore) {
  assertCampaignDesign(scenarios, reps);
  const expectedCellIds = designedCellIds(scenarios, reps);
  const cellsById = /* @__PURE__ */ new Map();
  for (const cell of cells) {
    const matches = cellsById.get(cell.cellId) ?? [];
    matches.push(cell);
    cellsById.set(cell.cellId, matches);
  }
  const scorableCellIds = [];
  const unscorableCells = [];
  for (const cellId of expectedCellIds) {
    const matches = cellsById.get(cellId) ?? [];
    if (matches.length === 0) {
      unscorableCells.push({ cellId, reason: "missing campaign cell" });
      continue;
    }
    if (matches.length > 1) {
      unscorableCells.push({ cellId, reason: `duplicate campaign cell (${matches.length})` });
      continue;
    }
    const cell = matches[0];
    const scoreEntries = Object.entries(cell.judgeScores);
    const successfulScores = scoreEntries.map(([, score]) => score).filter((score) => score.failed !== true && Number.isFinite(score.composite));
    const nonFiniteScores = scoreEntries.filter(
      ([, score]) => score.failed !== true && (!Number.isFinite(score.composite) || Object.values(score.dimensions).some((value) => !Number.isFinite(value)))
    );
    const reasons = [];
    if (cell.error) reasons.push(cell.error);
    if (cell.artifact === null || cell.artifact === void 0) reasons.push("missing artifact");
    if (!cell.error && requireJudgeScore && successfulScores.length === 0) {
      reasons.push("no successful finite judge score");
    }
    if (scoreEntries.some(([, score]) => score.failed === true)) {
      reasons.push("judge score marked failed");
    }
    if (nonFiniteScores.length > 0) {
      reasons.push(
        `non-finite judge score: ${nonFiniteScores.map(([name]) => name).sort().join(", ")}`
      );
    }
    if (reasons.length > 0) {
      unscorableCells.push({ cellId, reason: reasons.join("; ") });
    } else {
      scorableCellIds.push(cellId);
    }
  }
  const expected = new Set(expectedCellIds);
  for (const cell of cells) {
    if (cell.cellId !== `${cell.scenarioId}:${cell.rep}`) {
      unscorableCells.push({
        cellId: cell.cellId,
        reason: "campaign cell id does not match scenario id and rep"
      });
      continue;
    }
    if (!expected.has(cell.cellId)) {
      unscorableCells.push({ cellId: cell.cellId, reason: "unexpected campaign cell" });
    }
  }
  return {
    complete: unscorableCells.length === 0 && scorableCellIds.length === expectedCellIds.length,
    expectedCellIds,
    scorableCellIds,
    unscorableCells
  };
}
function formatCoverageFailures(coverage) {
  const shown = coverage.unscorableCells.slice(0, 3).map((cell) => `${cell.cellId}: ${cell.reason}`).join("; ");
  const remainder = coverage.unscorableCells.length - Math.min(3, coverage.unscorableCells.length);
  return remainder > 0 ? `${shown}; +${remainder} more` : shown || "unknown coverage failure";
}
function designedCellIds(scenarios, reps) {
  const ids = [];
  for (const scenario of scenarios) {
    for (let rep = 0; rep < reps; rep++) ids.push(`${scenario.id}:${rep}`);
  }
  return ids;
}

// src/campaign/run-dir.ts
import { homedir } from "os";
import { basename, isAbsolute, join } from "path";
function tangleTracesRoot() {
  return join(homedir(), ".tangle", "traces");
}
function resolveRunDir(runDir, repo) {
  if (isAbsolute(runDir) || runDir.startsWith("mem://")) return runDir;
  const r = repo && repo.trim().length > 0 ? repo : basename(process.cwd());
  return join(tangleTracesRoot(), r, "runs", runDir);
}

// src/campaign/storage.ts
import { createRequire } from "module";
import { join as join2 } from "path";

// src/campaign/search-ledger-file.ts
import { closeSync as closeSync2, constants as constants2, fsyncSync as fsyncSync2, mkdirSync, openSync as openSync2, writeSync } from "fs";
import { dirname } from "path";

// src/campaign/atomic-file-lock.ts
import { randomUUID } from "crypto";
import {
  closeSync,
  constants,
  existsSync as existsSync2,
  fsyncSync,
  linkSync,
  openSync,
  readFileSync as readFileSync2,
  renameSync,
  unlinkSync,
  writeFileSync
} from "fs";
import { hostname } from "os";
var AtomicFileLockError = class extends Error {
  name = "AtomicFileLockError";
};
function probeAtomicFileLock(options) {
  if (existsSync2(recoveryPath(options.lockPath))) return { acquired: false, reason: "recovery" };
  const state = ownerState(options.lockPath, options.acceptLegacyPid ?? false);
  if (state.state === "held") {
    return { acquired: false, reason: "held", holder: state.owner };
  }
  return null;
}
function tryAcquireAtomicFileLock(options) {
  const pid = options.pid ?? process.pid;
  if (!Number.isSafeInteger(pid) || pid <= 0) {
    throw new AtomicFileLockError("atomic file lock pid must be a positive integer");
  }
  const owner = { pid, host: hostname(), nonce: randomUUID() };
  const acceptLegacyPid = options.acceptLegacyPid ?? false;
  for (let attempt = 0; attempt < 8; attempt += 1) {
    if (existsSync2(recoveryPath(options.lockPath))) {
      return { acquired: false, reason: "recovery" };
    }
    if (tryLinkOwner(options.lockPath, owner, `acquire.${attempt}`)) {
      return acquired(options.lockPath, owner);
    }
    const holder = ownerState(options.lockPath, acceptLegacyPid);
    if (holder.state === "missing") continue;
    if (holder.state === "held") {
      return { acquired: false, reason: "held", holder: holder.owner };
    }
    const reclaimPath = recoveryPath(options.lockPath);
    if (!tryLinkOwner(reclaimPath, owner, `reclaim.${attempt}`)) {
      return { acquired: false, reason: "recovery" };
    }
    try {
      const current = ownerState(options.lockPath, acceptLegacyPid);
      if (current.state === "held") {
        return { acquired: false, reason: "held", holder: current.owner };
      }
      if (current.state === "stale") {
        const tombstone = `${options.lockPath}.stale.${owner.nonce}.${attempt}`;
        try {
          renameSync(options.lockPath, tombstone);
          unlinkSync(tombstone);
        } catch (error) {
          if (!isMissing(error)) throw error;
        }
      }
      if (tryLinkOwner(options.lockPath, owner, `recovered.${attempt}`)) {
        return acquired(options.lockPath, owner);
      }
    } finally {
      releaseOwnedPath(reclaimPath, owner);
    }
  }
  throw new AtomicFileLockError(`could not acquire atomic file lock ${options.lockPath}`);
}
function acquired(lockPath, owner) {
  return {
    acquired: true,
    lock: {
      owner,
      release: () => releaseOwnedPath(lockPath, owner)
    }
  };
}
function tryLinkOwner(lockPath, owner, suffix) {
  const ownerPath = `${lockPath}.${owner.pid}.${owner.nonce}.${suffix}.owner`;
  const descriptor = openSync(
    ownerPath,
    constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
    384
  );
  try {
    writeFileSync(descriptor, `${canonicalOwner(owner)}
`, "utf8");
    fsyncSync(descriptor);
  } catch (error) {
    closeSync(descriptor);
    unlinkIfExists(ownerPath);
    throw error;
  }
  closeSync(descriptor);
  try {
    linkSync(ownerPath, lockPath);
    return true;
  } catch (error) {
    if (!isAlreadyExists(error)) throw error;
    return false;
  } finally {
    unlinkIfExists(ownerPath);
  }
}
function ownerState(lockPath, acceptLegacyPid) {
  const owner = readOwner(lockPath, acceptLegacyPid);
  if (!owner) return { state: "missing" };
  if (owner.host !== hostname()) return { state: "held", owner };
  try {
    process.kill(owner.pid, 0);
    return { state: "held", owner };
  } catch (error) {
    if (isNoSuchProcess(error)) return { state: "stale", owner };
    return { state: "held", owner };
  }
}
function readOwner(lockPath, acceptLegacyPid) {
  let contents;
  try {
    contents = readFileSync2(lockPath, "utf8").trim();
  } catch (error) {
    if (isMissing(error)) return void 0;
    throw error;
  }
  if (acceptLegacyPid && /^[1-9]\d*$/.test(contents)) {
    const pid = Number(contents);
    if (Number.isSafeInteger(pid)) return { pid, host: hostname(), nonce: "legacy-pid" };
  }
  let value;
  try {
    value = JSON.parse(contents);
  } catch (error) {
    throw invalidOwner(lockPath, error);
  }
  if (!isOwner(value)) throw invalidOwner(lockPath);
  return value;
}
function isOwner(value) {
  if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
  const record = value;
  return Object.keys(record).length === 3 && Number.isSafeInteger(record.pid) && Number(record.pid) > 0 && typeof record.host === "string" && record.host.trim().length > 0 && typeof record.nonce === "string" && record.nonce.trim().length > 0;
}
function invalidOwner(lockPath, cause) {
  const error = new AtomicFileLockError(
    `atomic file lock has an invalid owner (${lockPath}); refusing unsafe recovery`
  );
  if (cause !== void 0) Object.defineProperty(error, "cause", { value: cause });
  return error;
}
function releaseOwnedPath(lockPath, owner) {
  const current = readOwner(lockPath, true);
  if (!current) return;
  if (canonicalOwner(current) !== canonicalOwner(owner)) {
    throw new AtomicFileLockError(`atomic file lock owner changed before release (${lockPath})`);
  }
  try {
    unlinkSync(lockPath);
  } catch (error) {
    if (!isMissing(error)) throw error;
  }
}
function canonicalOwner(owner) {
  return JSON.stringify({ host: owner.host, nonce: owner.nonce, pid: owner.pid });
}
function recoveryPath(lockPath) {
  return `${lockPath}.reclaim`;
}
function isAlreadyExists(error) {
  return error instanceof Error && "code" in error && error.code === "EEXIST";
}
function isMissing(error) {
  return error instanceof Error && "code" in error && error.code === "ENOENT";
}
function isNoSuchProcess(error) {
  return error instanceof Error && "code" in error && error.code === "ESRCH";
}
function unlinkIfExists(path) {
  try {
    unlinkSync(path);
  } catch (error) {
    if (!isMissing(error)) throw error;
  }
}

// src/campaign/search-ledger-errors.ts
var SearchLedgerError = class extends ValidationError {
};
var SearchLedgerIntegrityError = class extends SearchLedgerError {
};
var SearchLedgerConflictError = class extends SearchLedgerError {
};

// src/campaign/search-ledger-file.ts
function appendSearchLedgerLine(path, line) {
  mkdirSync(dirname(path), { recursive: true });
  const fd = openSync2(path, constants2.O_CREAT | constants2.O_WRONLY | constants2.O_APPEND, 384);
  try {
    writeAll(fd, Buffer.from(line, "utf8"));
    fsyncSync2(fd);
  } finally {
    closeSync2(fd);
  }
  fsyncDirectory(dirname(path));
}
function withSearchLedgerFileLock(ledgerPath, run) {
  const result = tryWithSearchLedgerFileLock(ledgerPath, run);
  if (!result.acquired) {
    throw new SearchLedgerIntegrityError(`search ledger lock is held (${ledgerPath})`);
  }
  return result.value;
}
function tryWithSearchLedgerFileLock(ledgerPath, run) {
  mkdirSync(dirname(ledgerPath), { recursive: true });
  const lockPath = `${ledgerPath}.lock`;
  try {
    const acquisition = tryAcquireAtomicFileLock({ lockPath });
    if (!acquisition.acquired) return { acquired: false };
    try {
      return { acquired: true, value: run() };
    } finally {
      acquisition.lock.release();
    }
  } catch (error) {
    if (error instanceof AtomicFileLockError) {
      throw new SearchLedgerIntegrityError(error.message, { cause: error });
    }
    throw error;
  }
}
function writeAll(fd, bytes) {
  let offset = 0;
  while (offset < bytes.byteLength) {
    const written = writeSync(fd, bytes, offset, bytes.byteLength - offset);
    if (written <= 0) throw new SearchLedgerIntegrityError("filesystem wrote zero bytes");
    offset += written;
  }
}
function fsyncDirectory(path) {
  const fd = openSync2(path, constants2.O_RDONLY);
  try {
    fsyncSync2(fd);
  } finally {
    closeSync2(fd);
  }
}

// src/campaign/storage.ts
function fsCampaignStorage() {
  const nodeRequire = createRequire(import.meta.url);
  const { existsSync: existsSync3, mkdirSync: mkdirSync2, readFileSync: readFileSync3, statSync, writeFileSync: writeFileSync2 } = nodeRequire(
    "node:fs"
  );
  return {
    ensureDir(dir) {
      if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
    },
    exists(path) {
      return existsSync3(path);
    },
    read(path) {
      try {
        return readFileSync3(path, "utf8");
      } catch {
        return void 0;
      }
    },
    write(path, content) {
      writeFileSync2(path, content);
    },
    append(path, content, expectedBytes) {
      const result = tryWithSearchLedgerFileLock(path, () => {
        let actualBytes = 0;
        try {
          actualBytes = statSync(path).size;
        } catch (error) {
          if (error.code !== "ENOENT") throw error;
        }
        if (actualBytes !== expectedBytes) return void 0;
        appendSearchLedgerLine(path, content);
        return expectedBytes + Buffer.byteLength(content);
      });
      return result.acquired ? result.value : void 0;
    }
  };
}
function inMemoryCampaignStorage() {
  const files = /* @__PURE__ */ new Map();
  const dirs = /* @__PURE__ */ new Set();
  return {
    ensureDir(dir) {
      dirs.add(dir);
    },
    exists(path) {
      return files.has(path) || dirs.has(path);
    },
    read(path) {
      const value = files.get(path);
      if (value === void 0) return void 0;
      return typeof value === "string" ? value : new TextDecoder().decode(value);
    },
    write(path, content) {
      files.set(path, content);
    },
    append(path, content, expectedBytes) {
      const current = files.get(path);
      const currentText = current === void 0 ? "" : typeof current === "string" ? current : new TextDecoder().decode(current);
      const currentBytes = new TextEncoder().encode(currentText).byteLength;
      if (currentBytes !== expectedBytes) return void 0;
      files.set(path, `${currentText}${content}`);
      return currentBytes + new TextEncoder().encode(content).byteLength;
    }
  };
}
function createRunCostLedger(input) {
  const path = join2(input.runDir, "cost-ledger.jsonl");
  input.storage.ensureDir(input.runDir);
  return new CostLedger({
    costCeilingUsd: input.costCeilingUsd,
    persistence: {
      read: () => {
        const stored = input.storage.read(path);
        if (stored === void 0 && input.storage.exists(path)) {
          throw new Error(`CostLedger: cannot read existing event log '${path}'`);
        }
        const events = stored ?? "";
        return {
          revision: String(new TextEncoder().encode(events).byteLength),
          events
        };
      },
      append: (expectedRevision, event) => {
        const expectedBytes = Number(expectedRevision);
        if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0) {
          throw new Error(`CostLedger: invalid storage revision '${expectedRevision}'`);
        }
        if (!input.storage.append) {
          throw new Error("CostLedger: CampaignStorage.append is required for paid calls");
        }
        const next = input.storage.append(path, event, expectedBytes);
        return next === void 0 ? void 0 : String(next);
      }
    }
  });
}

// src/campaign/run-campaign.ts
async function runCampaign(opts) {
  const seed = opts.seed ?? 42;
  const reps = opts.reps ?? 1;
  const resumable = opts.resumable ?? true;
  const now = opts.now ?? (() => /* @__PURE__ */ new Date());
  const judges = opts.judges ?? [];
  const storage = opts.storage ?? fsCampaignStorage();
  const costPhase = opts.costPhase ?? "campaign";
  assertCampaignDesign(opts.scenarios, reps);
  if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
    throw new Error("runCampaign: runDir is required and must be a non-empty string");
  }
  opts.runDir = resolveRunDir(opts.runDir, opts.repo);
  storage.ensureDir(opts.runDir);
  const costLedger = opts.costLedger ?? createRunCostLedger({
    storage,
    runDir: opts.runDir,
    costCeilingUsd: opts.costCeiling
  });
  if (opts.costCeiling !== void 0 && costLedger.costCeilingUsd !== opts.costCeiling) {
    throw new Error("runCampaign: costCeiling must match the shared CostLedger ceiling");
  }
  const maxConcurrency = opts.maxConcurrency ?? 2;
  const manifestHash = computeManifestHash({
    scenarios: opts.scenarios,
    judges,
    dispatchRef: dispatchRefFor(opts.dispatch, opts.dispatchRef),
    seed,
    reps
  });
  const splitDigest = campaignSplitDigest(opts.scenarios, reps);
  const startedAt = now();
  const runAttemptId = globalThis.crypto.randomUUID();
  const cells = [];
  const artifactsByPath = {};
  const schedule = buildCellSchedule(opts.scenarios, seed, reps);
  const abortController = new AbortController();
  const lanes = [];
  let nextIdx = 0;
  const cellsRef = cells;
  for (let i = 0; i < maxConcurrency; i++) {
    lanes.push(
      (async () => {
        while (true) {
          const myIdx = nextIdx++;
          if (myIdx >= schedule.length) return;
          const slot = schedule[myIdx];
          const result = await executeCell({
            slot,
            opts,
            manifestHash,
            resumable,
            now,
            storage,
            buildTraceWriter: opts.buildTraceWriter ?? defaultBuildTraceWriter(storage),
            signal: abortController.signal,
            dispatchTimeoutMs: opts.dispatchTimeoutMs,
            costLedger,
            costPhase,
            runAttemptId
          });
          cellsRef.push(result.cell);
          Object.assign(artifactsByPath, result.artifactsByPath);
          if (opts.labeledStore && opts.labeledStore !== "off" && !result.cell.error) {
            await captureToStore({
              store: opts.labeledStore,
              cell: result.cell,
              scenario: slot.scenario,
              opts,
              now
            }).catch((err) => {
              console.warn(
                `[runCampaign] capture failed for ${result.cell.cellId}: ${err instanceof Error ? err.message : String(err)}`
              );
            });
          }
        }
      })()
    );
  }
  await Promise.all(lanes);
  const endedAt = now();
  cellsRef.sort((a, b) => a.cellId.localeCompare(b.cellId));
  const campaignCost = costLedger.summary({ tags: { runDir: opts.runDir } });
  const aggregates = computeAggregates(
    cellsRef,
    judges,
    seed,
    campaignCost
  );
  return {
    manifestHash,
    splitDigest,
    seed,
    reps,
    startedAt: startedAt.toISOString(),
    endedAt: endedAt.toISOString(),
    durationMs: endedAt.getTime() - startedAt.getTime(),
    cells: cellsRef,
    aggregates,
    runDir: opts.runDir,
    artifactsByPath,
    scenarios: opts.scenarios.map(campaignScenarioIdentity)
  };
}
async function executeCell(args) {
  const storage = args.storage;
  const cellDir = join3(args.opts.runDir, args.slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"));
  storage.ensureDir(cellDir);
  const stableCostTags = {
    runDir: args.opts.runDir,
    cellId: args.slot.cellId,
    scenarioId: args.slot.scenario.id,
    rep: String(args.slot.rep)
  };
  const costTags = { ...stableCostTags, runAttemptId: args.runAttemptId };
  const cachePath = join3(cellDir, "cached-result.json");
  if (args.resumable) {
    const cached = readCachedCell({
      storage,
      cachePath,
      cellId: args.slot.cellId,
      manifestHash: args.manifestHash
    });
    if (cached.status === "hit") {
      enforceDispatchUsage(cached.cell, args.opts.expectUsage ?? "warn");
      const cachedHasUsage = cached.cell.costUsd > 0 || cached.cell.tokenUsage.input > 0 || cached.cell.tokenUsage.output > 0;
      if (cached.cell.costCallIds === void 0) {
        if (cachedHasUsage || Object.keys(cached.cell.judgeScores).length > 0) {
          throw new CostAccountingIncompleteError(
            `runCampaign: cached cell '${args.slot.cellId}' does not identify its ledger receipts`
          );
        }
      } else if (!Array.isArray(cached.cell.costCallIds) || cached.cell.costCallIds.some(
        (callId) => typeof callId !== "string" || callId.trim().length === 0
      ) || new Set(cached.cell.costCallIds).size !== cached.cell.costCallIds.length) {
        throw new CostAccountingIncompleteError(
          `runCampaign: cached cell '${args.slot.cellId}' has invalid ledger receipt IDs`
        );
      } else {
        const restoredCallIds = new Set(
          args.costLedger.list({ tags: stableCostTags }).map((receipt) => receipt.callId)
        );
        const missingCallIds = cached.cell.costCallIds.filter(
          (callId) => !restoredCallIds.has(callId)
        );
        if (missingCallIds.length > 0) {
          throw new CostAccountingIncompleteError(
            `runCampaign: cached cell '${args.slot.cellId}' is missing ledger receipt(s): ${missingCallIds.join(", ")}`
          );
        }
      }
      return { cell: { ...cached.cell, cached: true }, artifactsByPath: {} };
    }
  }
  const startMs = Date.now();
  const trace = args.buildTraceWriter(args.slot.cellId, cellDir);
  const artifactsByPath = {};
  const artifacts = {
    async write(path, content) {
      const fullPath = join3(cellDir, path);
      storage.ensureDir(join3(fullPath, ".."));
      storage.write(fullPath, content);
      artifactsByPath[`${args.slot.cellId}/${path}`] = fullPath;
      return fullPath;
    },
    async writeJson(path, value) {
      return artifacts.write(path, JSON.stringify(value, null, 2));
    }
  };
  const cost = {
    async runPaidCall(input) {
      const result = await args.costLedger.runPaidCall({
        ...input,
        channel: input.channel ?? "agent",
        phase: args.costPhase,
        actor: input.actor,
        tags: costTags,
        signal: cellAbort.signal
      });
      if (result.receipt) {
        trace.span(`cost.${result.receipt.actor}`, { amountUsd: result.receipt.costUsd }).end();
      }
      return result;
    }
  };
  const placement = args.opts.cellPlacement?.({
    scenario: args.slot.scenario,
    rep: args.slot.rep
  });
  const cellAbort = new AbortController();
  const onCampaignAbort = () => cellAbort.abort(args.signal.reason);
  if (args.signal.aborted) cellAbort.abort(args.signal.reason);
  else args.signal.addEventListener("abort", onCampaignAbort, { once: true });
  const ctx = {
    cellId: args.slot.cellId,
    rep: args.slot.rep,
    seed: args.slot.cellSeed,
    signal: cellAbort.signal,
    trace,
    artifacts,
    cost,
    placement
  };
  let artifact;
  let errorMessage;
  const timeoutMs = args.dispatchTimeoutMs;
  let timeoutTimer;
  try {
    const dispatched = args.opts.dispatch(args.slot.scenario, ctx);
    if (timeoutMs !== void 0 && timeoutMs > 0) {
      artifact = await Promise.race([
        dispatched,
        new Promise((_, reject) => {
          timeoutTimer = setTimeout(() => {
            cellAbort.abort(new Error("dispatch timeout"));
            reject(
              new Error(
                `dispatch exceeded ${timeoutMs}ms for cell '${args.slot.cellId}' \u2014 aborted and failed loud (no silent hang)`
              )
            );
          }, timeoutMs);
          if (typeof timeoutTimer.unref === "function")
            timeoutTimer.unref();
        })
      ]);
    } else {
      artifact = await dispatched;
    }
  } catch (err) {
    errorMessage = err instanceof Error ? err.message : String(err);
  } finally {
    if (timeoutTimer) clearTimeout(timeoutTimer);
    args.signal.removeEventListener("abort", onCampaignAbort);
  }
  const agentReceipts = args.costLedger.list({ channel: "agent", tags: costTags });
  const agentCost = args.costLedger.summary({ channel: "agent", tags: costTags });
  const tokenUsage = {
    input: agentCost.inputTokens,
    output: agentCost.outputTokens,
    ...agentCost.cachedTokens > 0 ? { cached: agentCost.cachedTokens } : {}
  };
  const resolvedModel = agentReceipts.at(-1)?.model;
  const dispatchResult = {
    cellId: args.slot.cellId,
    artifact,
    error: errorMessage,
    costUsd: agentCost.totalCostUsd,
    tokenUsage
  };
  try {
    enforceDispatchUsage(dispatchResult, args.opts.expectUsage ?? "warn");
  } catch (error) {
    await trace.flush();
    throw error;
  }
  const judgeScores = {};
  if (artifact !== void 0) {
    for (const judge of args.opts.judges ?? []) {
      if (judge.appliesTo && !judge.appliesTo(args.slot.scenario)) continue;
      try {
        const score = await runJudgeCell(judge, {
          artifact,
          scenario: args.slot.scenario,
          signal: args.signal,
          costLedger: args.costLedger,
          costPhase: args.costPhase,
          costTags
        });
        judgeScores[judge.name] = score;
      } catch (err) {
        if (err instanceof CostAccountingIncompleteError) {
          await trace.flush();
          throw err;
        }
        errorMessage = `judge '${judge.name}' failed: ${err instanceof Error ? err.message : String(err)}`;
        break;
      }
    }
  }
  await trace.flush();
  const costCallIds = args.costLedger.list({ tags: costTags }).map((receipt) => receipt.callId).sort();
  const cell = {
    manifestHash: args.manifestHash,
    cellId: args.slot.cellId,
    scenarioId: args.slot.scenario.id,
    rep: args.slot.rep,
    artifact: artifact ?? null,
    judgeScores,
    costUsd: agentCost.totalCostUsd,
    costEstimated: agentReceipts.some(
      (receipt) => receipt.actualCostUsd === void 0 && !receipt.costUnknown
    ),
    costCallIds,
    tokenUsage,
    ...resolvedModel ? { resolvedModel } : {},
    durationMs: Date.now() - startMs,
    seed: args.slot.cellSeed,
    cached: false,
    error: errorMessage
  };
  if (!errorMessage && args.resumable) {
    storage.write(cachePath, JSON.stringify(cell));
  }
  return { cell, artifactsByPath };
}
function planCampaignRun(opts) {
  const seed = opts.seed ?? 42;
  const reps = opts.reps ?? 1;
  const resumable = opts.resumable ?? true;
  const storage = opts.storage ?? fsCampaignStorage();
  assertCampaignDesign(opts.scenarios, reps);
  if (typeof opts.runDir !== "string" || opts.runDir.trim().length === 0) {
    throw new Error("planCampaignRun: runDir is required and must be a non-empty string");
  }
  opts.runDir = resolveRunDir(opts.runDir, opts.repo);
  const manifestHash = computeManifestHash({
    scenarios: opts.scenarios,
    judges: opts.judges ?? [],
    dispatchRef: dispatchRefFor(opts.dispatch, opts.dispatchRef),
    seed,
    reps
  });
  const splitDigest = campaignSplitDigest(opts.scenarios, reps);
  const cells = buildCellSchedule(opts.scenarios, seed, reps).map((slot) => {
    const cachePath = join3(
      opts.runDir,
      slot.cellId.replace(/[^a-zA-Z0-9_-]/g, "_"),
      "cached-result.json"
    );
    if (!resumable) {
      return {
        cellId: slot.cellId,
        scenarioId: slot.scenario.id,
        rep: slot.rep,
        seed: slot.cellSeed,
        cachePath,
        status: "run",
        reason: "resumable-off"
      };
    }
    const cached = readCachedCell({
      storage,
      cachePath,
      cellId: slot.cellId,
      manifestHash
    });
    if (cached.status === "hit") {
      return {
        cellId: slot.cellId,
        scenarioId: slot.scenario.id,
        rep: slot.rep,
        seed: slot.cellSeed,
        cachePath,
        status: "cached"
      };
    }
    return {
      cellId: slot.cellId,
      scenarioId: slot.scenario.id,
      rep: slot.rep,
      seed: slot.cellSeed,
      cachePath,
      status: "run",
      reason: cached.reason
    };
  });
  const cellsCached = cells.filter((cell) => cell.status === "cached").length;
  return {
    manifestHash,
    splitDigest,
    totalCells: cells.length,
    cellsCached,
    cellsToRun: cells.length - cellsCached,
    cells
  };
}
function enforceDispatchUsage(cell, mode) {
  if (mode === "off") return;
  if (cell.artifact === null || cell.artifact === void 0) return;
  const zeroTokens = cell.tokenUsage.input === 0 && cell.tokenUsage.output === 0;
  if (cell.costUsd !== 0 || !zeroTokens) return;
  const msg = `cell '${cell.cellId}' produced an artifact but reported zero cost and zero tokens \u2014 the dispatch made no paid call through ctx.cost.runPaidCall (a stub cell)`;
  if (mode === "assert") {
    const report = {
      totalRecords: 1,
      stubRecords: 1,
      realRecords: 0,
      uncostedRecords: 0,
      totalInputTokens: 0,
      totalOutputTokens: 0,
      totalCostUsd: 0,
      verdict: "stub",
      diagnosis: msg
    };
    throw new BackendIntegrityError(`expectUsage: ${msg}`, report);
  }
  console.warn(`[runCampaign] expectUsage: ${msg}`);
}
async function runJudgeCell(judge, input) {
  const previousJudgeCalls = new Set(
    input.costLedger?.list({ channel: "judge", tags: input.costTags }).map((receipt) => receipt.callId) ?? []
  );
  try {
    const score = await judge.score(input);
    assertReportedJudgeCallRecorded(judge.name, score, input, previousJudgeCalls);
    return score;
  } catch (error) {
    assertReportedJudgeCallRecorded(judge.name, error, input, previousJudgeCalls, error);
    throw error;
  }
}
function assertReportedJudgeCallRecorded(judgeName, value, input, previousCallIds, cause) {
  if (!hasLlmCall(value)) return;
  const recorded = input.costLedger?.list({ channel: "judge", tags: input.costTags }).some((receipt) => !previousCallIds.has(receipt.callId));
  if (recorded) return;
  throw new CostAccountingIncompleteError(
    `runCampaign: judge '${judgeName}' reported a paid LLM call without a CostLedger receipt`,
    cause === void 0 ? void 0 : { cause }
  );
}
function hasLlmCall(value) {
  return typeof value === "object" && value !== null && "llmCall" in value && value.llmCall !== void 0;
}
function defaultBuildTraceWriter(storage) {
  return (cellId, dir) => {
    const spans = [];
    return {
      span(name, attributes) {
        const startMs = Date.now();
        const record = { name, cellId, startMs, ...attributes ?? {} };
        const finish = {
          end(endAttrs) {
            record.durationMs = Date.now() - startMs;
            if (endAttrs) Object.assign(record, endAttrs);
            spans.push(record);
          },
          setAttribute(key, value) {
            record[key] = value;
          }
        };
        return finish;
      },
      async flush() {
        storage.write(join3(dir, "spans.jsonl"), spans.map((s) => JSON.stringify(s)).join("\n"));
      }
    };
  };
}
function buildCellSchedule(scenarios, seed, reps) {
  const schedule = [];
  let cellIndex = 0;
  for (const scenario of scenarios) {
    for (let rep = 0; rep < reps; rep++) {
      const cellId = `${scenario.id}:${rep}`;
      const cellSeed = seed + cellIndex;
      schedule.push({ scenario, rep, cellId, cellSeed });
      cellIndex += 1;
    }
  }
  return schedule;
}
function dispatchRefFor(dispatch, override) {
  const ref = override ?? dispatch?.name ?? "anonymous";
  if (typeof ref !== "string" || ref.trim().length === 0) {
    throw new Error("runCampaign: dispatchRef must be a non-empty string when provided");
  }
  return ref;
}
function readCachedCell(args) {
  const raw = args.storage.read(args.cachePath);
  if (raw === void 0) return { status: "miss", reason: "missing" };
  try {
    const cached = JSON.parse(raw);
    if (cached.cellId !== args.cellId) return { status: "miss", reason: "cell-mismatch" };
    if (cached.manifestHash !== args.manifestHash) {
      return { status: "miss", reason: "manifest-mismatch" };
    }
    return { status: "hit", cell: cached };
  } catch {
    return { status: "miss", reason: "corrupt" };
  }
}
async function captureToStore(args) {
  await args.store.observe({
    scenario: args.scenario,
    artifact: args.cell.artifact,
    judgeScores: args.cell.judgeScores,
    source: args.opts.captureSource ?? "eval-run",
    sourceVersionHash: args.opts.captureSourceVersionHash ?? "unknown",
    capturedAt: args.now().toISOString(),
    redactionStatus: "raw"
  });
}
function computeManifestHash(input) {
  return contentHash({
    scenarios: input.scenarios,
    judges: input.judges.map((judge) => ({
      name: judge.name,
      dims: judge.dimensions,
      version: judgeVersionFor(judge)
    })),
    dispatch: input.dispatchRef,
    seed: input.seed,
    reps: input.reps
  });
}
function judgeVersionFor(judge) {
  if (judge.judgeVersion !== void 0) {
    const version = judge.judgeVersion.trim();
    if (version.length === 0) {
      throw new Error(`runCampaign: judge '${judge.name}' has an empty judgeVersion`);
    }
    return version;
  }
  return contentHash({
    score: judge.score.toString(),
    appliesTo: judge.appliesTo?.toString() ?? null
  });
}
function computeAggregates(cells, judges, seed, cost) {
  const byJudge = {};
  for (const judge of judges) {
    const scores = [];
    for (const cell of cells) {
      const s = cell.judgeScores[judge.name];
      if (s !== void 0) scores.push(s.composite);
    }
    byJudge[judge.name] = aggregate(scores, seed);
  }
  const byScenario = {};
  const scenarioGroups = /* @__PURE__ */ new Map();
  for (const cell of cells) {
    const composites = Object.values(cell.judgeScores).map((s) => s.composite);
    if (composites.length === 0) continue;
    const mean = composites.reduce((a, b) => a + b, 0) / composites.length;
    const arr = scenarioGroups.get(cell.scenarioId) ?? [];
    arr.push(mean);
    scenarioGroups.set(cell.scenarioId, arr);
  }
  for (const [scenarioId, samples] of scenarioGroups) {
    const ag = aggregate(samples, seed);
    byScenario[scenarioId] = { meanComposite: ag.mean, ci95: ag.ci95, n: ag.n };
  }
  return {
    byJudge,
    byScenario,
    cost,
    totalCostUsd: cost.totalCostUsd,
    cellsExecuted: cells.filter((c) => !c.error).length,
    cellsSkipped: cells.filter((c) => c.error?.startsWith("skipped:")).length,
    cellsCached: cells.filter((c) => c.cached).length,
    cellsFailed: cells.filter((c) => c.error && !c.error.startsWith("skipped:")).length
  };
}
function aggregate(samples, seed) {
  const n = samples.length;
  if (n === 0) return { mean: 0, stdev: 0, ci95: [0, 0], n: 0 };
  const mean = samples.reduce((a, b) => a + b, 0) / n;
  const variance = samples.reduce((a, b) => a + (b - mean) ** 2, 0) / Math.max(1, n - 1);
  const stdev = Math.sqrt(variance);
  const ci = confidenceInterval(samples, 0.95, { seed, resamples: 1e3 });
  return { mean, stdev, ci95: [ci.lower, ci.upper], n };
}

export {
  BackendIntegrityError,
  summarizeBackendIntegrity,
  summarizeAgentReceiptIntegrity,
  assertRealBackend,
  assertRealAgentReceipts,
  canonicalJson,
  contentHash,
  inMemoryVerdictCache,
  fileVerdictCache,
  cachedJudge,
  assertCampaignDesign,
  campaignScenarioIdentity,
  campaignSplitDigestFromIdentities,
  campaignSplitDigest,
  assertCampaignSplitIdentity,
  campaignCoverage,
  formatCoverageFailures,
  tangleTracesRoot,
  resolveRunDir,
  probeAtomicFileLock,
  tryAcquireAtomicFileLock,
  SearchLedgerError,
  SearchLedgerIntegrityError,
  SearchLedgerConflictError,
  appendSearchLedgerLine,
  withSearchLedgerFileLock,
  fsCampaignStorage,
  inMemoryCampaignStorage,
  createRunCostLedger,
  runCampaign,
  planCampaignRun
};
//# sourceMappingURL=chunk-N3QPYIVG.js.map