// src/rollout/schema.ts
var ROLLOUT_SCHEMA = "tangle.rollout.v1";
var ROLLOUT_FORMAT = ROLLOUT_SCHEMA;
var ROLLOUT_ROLES = [
  "agent",
  "supervisor",
  "worker",
  "proposer",
  "judge",
  "analyst"
];
var ROLLOUT_SPLITS = [
  "search",
  "dev",
  "holdout",
  "canary",
  "train"
];
var TRAINABLE_SPLITS = ["search", "train"];
function isTrainableSplit(split) {
  return TRAINABLE_SPLITS.includes(split);
}
var ROLLOUT_CAPTURES = ["mint", "settle-time", "backfill"];
var CHAT_ROLES = ["system", "user", "assistant", "tool"];
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
var isNumberOrNull = (v) => v === null || typeof v === "number";
var isStringOrNull = (v) => v === null || typeof v === "string";
var isIntegerOrNull = (v) => v === null || Number.isInteger(v);
function validateChatMessage(value, path, errors) {
  if (!isRecord(value)) {
    errors.push(`${path}: not an object`);
    return;
  }
  if (!CHAT_ROLES.includes(value.role))
    errors.push(`${path}.role: invalid role ${String(value.role)}`);
  if (!isStringOrNull(value.content)) errors.push(`${path}.content: must be string|null`);
  if (value.reasoning_content !== void 0 && typeof value.reasoning_content !== "string") {
    errors.push(`${path}.reasoning_content: must be string when present`);
  }
  if (value.tool_call_id !== void 0 && typeof value.tool_call_id !== "string") {
    errors.push(`${path}.tool_call_id: must be string when present`);
  }
  if (value.role === "tool" && typeof value.tool_call_id !== "string") {
    errors.push(`${path}.tool_call_id: required on role:"tool"`);
  }
  if (value.tool_calls !== void 0) {
    if (!Array.isArray(value.tool_calls)) {
      errors.push(`${path}.tool_calls: must be an array when present`);
    } else {
      value.tool_calls.forEach((call, i) => {
        if (!isRecord(call) || typeof call.id !== "string" || call.type !== "function") {
          errors.push(`${path}.tool_calls[${i}]: must be {id, type:"function", function}`);
          return;
        }
        const fn = call.function;
        if (!isRecord(fn) || typeof fn.name !== "string" || typeof fn.arguments !== "string") {
          errors.push(
            `${path}.tool_calls[${i}].function: must be {name: string, arguments: string}`
          );
        }
      });
    }
  }
}
function validateSection(value, path, fields, errors) {
  if (!isRecord(value)) {
    errors.push(`${path}: not an object`);
    return;
  }
  for (const [name, check, expect] of fields) {
    if (!check(value[name])) errors.push(`${path}.${name}: expected ${expect}`);
  }
}
function validateRolloutLine(value) {
  const errors = [];
  if (!isRecord(value)) return ["line: not an object"];
  if (value.schema !== ROLLOUT_SCHEMA) errors.push(`schema: expected "${ROLLOUT_SCHEMA}"`);
  if (typeof value.rollout_id !== "string" || value.rollout_id.length === 0)
    errors.push("rollout_id: expected non-empty string");
  if (!isStringOrNull(value.parent_rollout_id))
    errors.push("parent_rollout_id: expected string|null");
  if (typeof value.run_id !== "string" || value.run_id.length === 0)
    errors.push("run_id: expected non-empty string");
  if (value.experiment_id !== void 0 && !isStringOrNull(value.experiment_id)) {
    errors.push("experiment_id: expected string|null when present");
  }
  if (value.candidate_id !== void 0 && !isStringOrNull(value.candidate_id)) {
    errors.push("candidate_id: expected string|null when present");
  }
  if (!isIntegerOrNull(value.generation)) errors.push("generation: expected integer|null");
  if (!isIntegerOrNull(value.candidate_index)) errors.push("candidate_index: expected integer|null");
  if (!ROLLOUT_ROLES.includes(value.role))
    errors.push(`role: invalid role ${String(value.role)}`);
  validateSection(
    value.task,
    "task",
    [
      ["suite", (v) => typeof v === "string" && v.length > 0, "non-empty string"],
      ["instance_id", (v) => typeof v === "string" && v.length > 0, "non-empty string"],
      [
        "split",
        (v) => ROLLOUT_SPLITS.includes(v),
        `one of ${ROLLOUT_SPLITS.join("|")}`
      ],
      ["seed", isNumberOrNull, "number|null"],
      ["rep", (v) => Number.isInteger(v), "integer"]
    ],
    errors
  );
  validateSection(
    value.policy,
    "policy",
    [
      ["harness", isStringOrNull, "string|null"],
      ["harness_version", isStringOrNull, "string|null"],
      ["model", isStringOrNull, "string|null"],
      ["provider", isStringOrNull, "string|null"],
      ["profile_commit", isStringOrNull, "string|null"],
      ["sampling", (v) => v === null || isRecord(v), "object|null"]
    ],
    errors
  );
  if (isRecord(value.policy)) {
    for (const key of ["prompt_hash", "config_hash", "agent_profile_cell_id"]) {
      if (value.policy[key] !== void 0 && !isStringOrNull(value.policy[key])) {
        errors.push(`policy.${key}: expected string|null when present`);
      }
    }
  }
  if (!Array.isArray(value.messages)) {
    errors.push("messages: expected array");
  } else {
    for (const [i, m] of value.messages.entries()) validateChatMessage(m, `messages[${i}]`, errors);
  }
  if (!Array.isArray(value.tool_defs)) {
    errors.push("tool_defs: expected array");
  } else {
    value.tool_defs.forEach((d, i) => {
      if (!isRecord(d) || d.type !== "function" || !isRecord(d.function) || typeof d.function.name !== "string") {
        errors.push(`tool_defs[${i}]: must be {type:"function", function:{name}}`);
      }
    });
  }
  if (value.steps !== void 0) {
    if (!Array.isArray(value.steps)) {
      errors.push("steps: expected array when present");
    } else {
      value.steps.forEach((s, i) => {
        if (!isRecord(s) || typeof s.kind !== "string" || typeof s.name !== "string") {
          errors.push(`steps[${i}]: must be {kind: string, name: string, \u2026}`);
        }
      });
    }
  }
  validateSection(
    value.outcome,
    "outcome",
    [
      ["reward", isNumberOrNull, "number|null"],
      ["reward_source", isStringOrNull, "string|null"],
      ["metrics", isRecord, "object"],
      ["is_completed", (v) => typeof v === "boolean", "boolean"],
      ["is_truncated", (v) => typeof v === "boolean", "boolean"],
      ["error", isStringOrNull, "string|null"]
    ],
    errors
  );
  if (isRecord(value.outcome)) {
    if (!("verdict" in value.outcome)) errors.push("outcome.verdict: field required (may be null)");
    if (value.outcome.realness_gated !== void 0 && typeof value.outcome.realness_gated !== "boolean") {
      errors.push("outcome.realness_gated: expected boolean when present");
    }
  }
  validateSection(
    value.cost,
    "cost",
    [
      ["usd", isNumberOrNull, "number|null"],
      ["tokens_in", isNumberOrNull, "number|null"],
      ["tokens_out", isNumberOrNull, "number|null"],
      ["tokens_reasoning", isNumberOrNull, "number|null"],
      ["cache_read", isNumberOrNull, "number|null"],
      ["cache_write", isNumberOrNull, "number|null"],
      ["wall_s", isNumberOrNull, "number|null"]
    ],
    errors
  );
  validateSection(
    value.artifacts,
    "artifacts",
    [
      ["patch_path", isStringOrNull, "string|null"],
      ["run_dir", isStringOrNull, "string|null"],
      ["transcript_ref", isStringOrNull, "string|null"]
    ],
    errors
  );
  validateSection(
    value.provenance,
    "provenance",
    [
      [
        "captured_at",
        (v) => typeof v === "string" && !Number.isNaN(Date.parse(v)),
        "ISO-8601 timestamp"
      ],
      [
        "capture",
        (v) => ROLLOUT_CAPTURES.includes(v),
        `one of ${ROLLOUT_CAPTURES.join("|")}`
      ]
    ],
    errors
  );
  if (isRecord(value.provenance) && value.provenance.gap !== void 0 && typeof value.provenance.gap !== "string") {
    errors.push("provenance.gap: must be string when present");
  }
  if (Array.isArray(value.messages) && isRecord(value.provenance)) {
    if (value.messages.length === 0 && typeof value.provenance.gap !== "string") {
      errors.push("provenance.gap: required when messages is empty");
    }
  }
  return errors;
}
function assertRolloutLine(value, context = "rollout line") {
  const errors = validateRolloutLine(value);
  if (errors.length > 0) {
    throw new Error(`invalid ${context}:
  ${errors.join("\n  ")}`);
  }
}
function isRolloutLine(value) {
  return validateRolloutLine(value).length === 0;
}

export {
  ROLLOUT_SCHEMA,
  ROLLOUT_FORMAT,
  ROLLOUT_ROLES,
  ROLLOUT_SPLITS,
  TRAINABLE_SPLITS,
  isTrainableSplit,
  ROLLOUT_CAPTURES,
  CHAT_ROLES,
  validateRolloutLine,
  assertRolloutLine,
  isRolloutLine
};
//# sourceMappingURL=chunk-MAX3TN3C.js.map