import {
  FAILURE_CLASSES
} from "./chunk-MA6HLL3S.js";
import {
  validateAgentProfileCell
} from "./chunk-XJYR7XFV.js";
import {
  ValidationError
} from "./chunk-ONWEPEDO.js";

// src/run-record.ts
var MANDATORY_TOP_LEVEL = [
  "runId",
  "experimentId",
  "candidateId",
  "seed",
  "model",
  "promptHash",
  "configHash",
  "commitSha",
  "wallMs",
  "costUsd",
  "tokenUsage",
  "outcome",
  "splitTag"
];
var SPLIT_TAGS = ["search", "dev", "holdout"];
var RunRecordValidationError = class extends ValidationError {
  path;
  constructor(message, path = "") {
    super(path ? `${message} (at ${path})` : message);
    this.path = path;
  }
};
function validateRunRecord(input) {
  if (input === null || typeof input !== "object") {
    throw new RunRecordValidationError("expected object");
  }
  const obj = input;
  for (const key of MANDATORY_TOP_LEVEL) {
    if (!(key in obj)) {
      throw new RunRecordValidationError(`missing mandatory field "${key}"`);
    }
  }
  expectString(obj.runId, "runId");
  expectString(obj.experimentId, "experimentId");
  expectString(obj.candidateId, "candidateId");
  expectFiniteNumber(obj.seed, "seed");
  expectString(obj.model, "model");
  expectString(obj.promptHash, "promptHash");
  expectString(obj.configHash, "configHash");
  expectString(obj.commitSha, "commitSha");
  expectNonNegativeNumber(obj.wallMs, "wallMs");
  if (obj.queueMs !== void 0) expectNonNegativeNumber(obj.queueMs, "queueMs");
  expectNonNegativeNumber(obj.costUsd, "costUsd");
  if (obj.costProvenance !== void 0) {
    validateCostProvenance(obj.costProvenance, obj.costUsd);
  }
  if (!modelHasSnapshot(obj.model)) {
    throw new RunRecordValidationError(
      `model "${obj.model}" lacks a snapshot version (use 'name@YYYY-MM-DD' or 'name-YYYYMMDD')`,
      "model"
    );
  }
  const tu = obj.tokenUsage;
  if (tu === null || typeof tu !== "object") {
    throw new RunRecordValidationError("tokenUsage must be an object", "tokenUsage");
  }
  const tuRec = tu;
  expectNonNegativeNumber(tuRec.input, "tokenUsage.input");
  expectNonNegativeNumber(tuRec.output, "tokenUsage.output");
  if (tuRec.reasoning !== void 0) {
    expectNonNegativeNumber(tuRec.reasoning, "tokenUsage.reasoning");
    if (tuRec.reasoning > tuRec.output) {
      throw new RunRecordValidationError(
        "reasoning tokens must be a subset of output tokens",
        "tokenUsage.reasoning"
      );
    }
  }
  if (tuRec.cached !== void 0) expectNonNegativeNumber(tuRec.cached, "tokenUsage.cached");
  if (tuRec.cacheWrite !== void 0) {
    expectNonNegativeNumber(tuRec.cacheWrite, "tokenUsage.cacheWrite");
  }
  if (obj.judgeMetadata !== void 0) {
    const jm = obj.judgeMetadata;
    if (jm === null || typeof jm !== "object") {
      throw new RunRecordValidationError("judgeMetadata must be an object", "judgeMetadata");
    }
    const jmRec = jm;
    expectString(jmRec.model, "judgeMetadata.model");
    expectString(jmRec.promptVersion, "judgeMetadata.promptVersion");
    expectFiniteNumber(jmRec.confidence, "judgeMetadata.confidence");
    if (typeof jmRec.fallback !== "boolean") {
      throw new RunRecordValidationError(
        "judgeMetadata.fallback must be boolean",
        "judgeMetadata.fallback"
      );
    }
  }
  const out = obj.outcome;
  if (out === null || typeof out !== "object") {
    throw new RunRecordValidationError("outcome must be an object", "outcome");
  }
  const outRec = out;
  if (outRec.searchScore !== void 0)
    expectFiniteNumber(outRec.searchScore, "outcome.searchScore");
  if (outRec.holdoutScore !== void 0)
    expectFiniteNumber(outRec.holdoutScore, "outcome.holdoutScore");
  if (outRec.searchScore === void 0 && outRec.holdoutScore === void 0) {
    throw new RunRecordValidationError(
      "outcome must define searchScore or holdoutScore (or both)",
      "outcome"
    );
  }
  const raw = outRec.raw;
  if (raw === null || typeof raw !== "object") {
    throw new RunRecordValidationError("outcome.raw must be an object", "outcome.raw");
  }
  for (const [k, v] of Object.entries(raw)) {
    expectFiniteNumber(v, `outcome.raw.${k}`);
  }
  if (outRec.realness !== void 0) {
    const r = outRec.realness;
    if (r === null || typeof r !== "object") {
      throw new RunRecordValidationError("outcome.realness must be an object", "outcome.realness");
    }
    const rr = r;
    expectFiniteNumber(rr.score, "outcome.realness.score");
    if (typeof rr.gated !== "boolean") {
      throw new RunRecordValidationError(
        "outcome.realness.gated must be a boolean",
        "outcome.realness.gated"
      );
    }
  }
  if (outRec.judgeScores !== void 0) {
    validateJudgeScores(outRec.judgeScores, "outcome.judgeScores");
  }
  if (obj.failureClass !== void 0 && (typeof obj.failureClass !== "string" || !FAILURE_CLASSES.includes(obj.failureClass))) {
    throw new RunRecordValidationError(
      `failureClass must be one of ${FAILURE_CLASSES.join(", ")}`,
      "failureClass"
    );
  }
  if (obj.failureMode !== void 0) expectString(obj.failureMode, "failureMode");
  if (obj.agentProfile !== void 0) {
    try {
      const profile = validateAgentProfileCell(obj.agentProfile);
      if (profile.model !== void 0 && profile.model !== obj.model) {
        throw new RunRecordValidationError(
          `agentProfile.model "${profile.model}" does not match model "${obj.model}"`,
          "agentProfile.model"
        );
      }
      if (profile.promptHash !== void 0 && profile.promptHash !== obj.promptHash) {
        throw new RunRecordValidationError(
          `agentProfile.promptHash "${profile.promptHash}" does not match promptHash "${obj.promptHash}"`,
          "agentProfile.promptHash"
        );
      }
    } catch (error) {
      if (error instanceof RunRecordValidationError) throw error;
      if (error instanceof Error) {
        throw new RunRecordValidationError(error.message, "agentProfile");
      }
      throw error;
    }
  }
  if (typeof obj.splitTag !== "string" || !SPLIT_TAGS.includes(obj.splitTag)) {
    throw new RunRecordValidationError(
      `splitTag must be one of ${SPLIT_TAGS.join(", ")}, got ${String(obj.splitTag)}`,
      "splitTag"
    );
  }
  return input;
}
function resolveRunCostProvenance(run) {
  if (run.costProvenance) return run.costProvenance;
  if (run.outcome.raw.cost_estimated === 1) {
    return { kind: "estimated", usd: run.costUsd };
  }
  if (run.costUsd > 0) return { kind: "observed", usd: run.costUsd };
  return { kind: "uncaptured", usd: null };
}
function validateCostProvenance(input, costUsd) {
  if (input === null || typeof input !== "object") {
    throw new RunRecordValidationError("costProvenance must be an object", "costProvenance");
  }
  const value = input;
  if (value.kind !== "observed" && value.kind !== "estimated" && value.kind !== "uncaptured") {
    throw new RunRecordValidationError(
      "costProvenance.kind must be observed, estimated, or uncaptured",
      "costProvenance.kind"
    );
  }
  if (value.kind === "uncaptured") {
    if (value.usd !== null) {
      throw new RunRecordValidationError(
        "uncaptured costProvenance.usd must be null",
        "costProvenance.usd"
      );
    }
    if (costUsd !== 0) {
      throw new RunRecordValidationError(
        "uncaptured costProvenance requires the compatibility costUsd sentinel 0",
        "costUsd"
      );
    }
    return;
  }
  expectFiniteNumber(value.usd, "costProvenance.usd");
  if (value.usd < 0) {
    throw new RunRecordValidationError(
      "costProvenance.usd must be non-negative",
      "costProvenance.usd"
    );
  }
  if (value.usd !== costUsd) {
    throw new RunRecordValidationError(
      "costProvenance.usd must equal costUsd",
      "costProvenance.usd"
    );
  }
}
function isRunRecord(input) {
  try {
    validateRunRecord(input);
    return true;
  } catch {
    return false;
  }
}
function parseRunRecordSafe(input) {
  try {
    return { ok: true, value: validateRunRecord(input) };
  } catch (e) {
    if (e instanceof RunRecordValidationError) return { ok: false, error: e };
    throw e;
  }
}
function roundTripRunRecord(record) {
  const json = JSON.stringify(record);
  return validateRunRecord(JSON.parse(json));
}
function expectString(value, path) {
  if (typeof value !== "string" || value.length === 0) {
    throw new RunRecordValidationError(`expected non-empty string`, path);
  }
}
function expectFiniteNumber(value, path) {
  if (typeof value !== "number" || !Number.isFinite(value)) {
    throw new RunRecordValidationError(`expected finite number`, path);
  }
}
function expectNonNegativeNumber(value, path) {
  expectFiniteNumber(value, path);
  if (value < 0) {
    throw new RunRecordValidationError("expected non-negative number", path);
  }
}
function validateJudgeScores(value, path) {
  if (value === null || typeof value !== "object") {
    throw new RunRecordValidationError("judgeScores must be an object", path);
  }
  const rec = value;
  const perJudge = rec.perJudge;
  if (perJudge === null || typeof perJudge !== "object") {
    throw new RunRecordValidationError("perJudge must be an object", `${path}.perJudge`);
  }
  for (const [judgeId, dims] of Object.entries(perJudge)) {
    if (dims === null || typeof dims !== "object") {
      throw new RunRecordValidationError(
        "per-judge entry must be an object of dimension scores",
        `${path}.perJudge.${judgeId}`
      );
    }
    for (const [dim, score] of Object.entries(dims)) {
      expectFiniteNumber(score, `${path}.perJudge.${judgeId}.${dim}`);
    }
  }
  const perDimMean = rec.perDimMean;
  if (perDimMean === null || typeof perDimMean !== "object") {
    throw new RunRecordValidationError("perDimMean must be an object", `${path}.perDimMean`);
  }
  for (const [dim, mean] of Object.entries(perDimMean)) {
    expectFiniteNumber(mean, `${path}.perDimMean.${dim}`);
  }
  expectFiniteNumber(rec.composite, `${path}.composite`);
  if (rec.failedJudges !== void 0) {
    if (!Array.isArray(rec.failedJudges)) {
      throw new RunRecordValidationError(
        "failedJudges must be an array of strings",
        `${path}.failedJudges`
      );
    }
    for (let i = 0; i < rec.failedJudges.length; i++) {
      const id = rec.failedJudges[i];
      if (typeof id !== "string" || id.length === 0) {
        throw new RunRecordValidationError(
          "failedJudges entry must be a non-empty string",
          `${path}.failedJudges[${i}]`
        );
      }
    }
  }
  if (rec.notes !== void 0 && typeof rec.notes !== "string") {
    throw new RunRecordValidationError("notes must be a string", `${path}.notes`);
  }
}
function modelHasSnapshot(model) {
  if (model.includes("@")) return true;
  if (/-\d{8}$/.test(model)) return true;
  if (/-\d{4}-\d{2}-\d{2}$/.test(model)) return true;
  if (/:date-/.test(model)) return true;
  return false;
}

export {
  RunRecordValidationError,
  validateRunRecord,
  resolveRunCostProvenance,
  isRunRecord,
  parseRunRecordSafe,
  roundTripRunRecord,
  modelHasSnapshot
};
//# sourceMappingURL=chunk-S3UZOQ5Y.js.map