import {
  ROLLOUT_ROLES,
  ROLLOUT_SCHEMA,
  assertRolloutLine,
  isTrainableSplit
} from "./chunk-MAX3TN3C.js";

// src/rollout/exporters.ts
function toSftRows(lines, options = {}) {
  const minReward = options.minReward ?? 1;
  return lines.filter(
    (line) => line.outcome.reward !== null && line.outcome.reward >= minReward && line.outcome.realness_gated !== true && isTrainableSplit(line.task.split) && line.messages.length > 0
  ).map((line) => ({
    messages: line.messages,
    metadata: {
      rollout_id: line.rollout_id,
      run_id: line.run_id,
      candidate_id: line.candidate_id ?? null,
      instance_id: line.task.instance_id,
      reward: line.outcome.reward
    }
  }));
}
function toRewardRows(lines) {
  return lines.filter((line) => line.outcome.reward !== null).map((line) => ({
    prompt: line.messages.find((m) => m.role === "user")?.content ?? "",
    steps: line.steps ?? [],
    reward: line.outcome.reward,
    metadata: {
      rollout_id: line.rollout_id,
      run_id: line.run_id,
      candidate_id: line.candidate_id ?? null,
      instance_id: line.task.instance_id,
      split: line.task.split
    }
  }));
}
function firstAssistantIndex(messages) {
  const index = messages.findIndex((m) => m.role === "assistant");
  return index === -1 ? messages.length : index;
}
function toVerifiersRolloutOutput(line) {
  const split = firstAssistantIndex(line.messages);
  return {
    prompt: line.messages.slice(0, split),
    completion: line.messages.slice(split),
    reward: line.outcome.reward,
    metrics: line.outcome.metrics,
    tool_defs: line.tool_defs,
    token_usage: {
      input_tokens: line.cost.tokens_in,
      output_tokens: line.cost.tokens_out,
      reasoning_tokens: line.cost.tokens_reasoning,
      cache_read_tokens: line.cost.cache_read,
      cache_write_tokens: line.cost.cache_write
    },
    info: {
      task: line.task,
      policy: line.policy,
      rollout_id: line.rollout_id,
      run_id: line.run_id,
      experiment_id: line.experiment_id ?? null,
      candidate_id: line.candidate_id ?? null,
      generation: line.generation,
      candidate_index: line.candidate_index,
      role: line.role
    }
  };
}
function toVerifiersRolloutOutputs(lines) {
  return lines.filter((line) => line.messages.length > 0).map(toVerifiersRolloutOutput);
}
function toRftItem(line) {
  const split = firstAssistantIndex(line.messages);
  return {
    messages: line.messages.slice(0, split),
    reference: {
      reward: line.outcome.reward,
      reward_source: line.outcome.reward_source,
      verdict: line.outcome.verdict,
      instance_id: line.task.instance_id,
      suite: line.task.suite,
      split: line.task.split,
      rollout_id: line.rollout_id
    }
  };
}
function toRftItems(lines) {
  return lines.filter((line) => line.messages.length > 0 && firstAssistantIndex(line.messages) > 0).map(toRftItem);
}
function toJsonl(rows) {
  return rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : "");
}

// src/rollout/ledger.ts
import { appendFile, mkdir, readFile, writeFile } from "fs/promises";
import { dirname } from "path";
function serialize(lines) {
  for (const [i, line] of lines.entries()) assertRolloutLine(line, `rollout line [${i}]`);
  return lines.map((line) => JSON.stringify(line)).join("\n") + (lines.length > 0 ? "\n" : "");
}
async function writeRolloutLedger(path, lines) {
  const payload = serialize(lines);
  await mkdir(dirname(path), { recursive: true });
  await writeFile(path, payload);
}
async function appendRolloutLines(path, lines) {
  if (lines.length === 0) return;
  const payload = serialize(lines);
  await mkdir(dirname(path), { recursive: true });
  await appendFile(path, payload);
}
async function readRolloutLedger(path) {
  const raw = await readFile(path, "utf8");
  const lines = [];
  const rawLines = raw.split("\n");
  for (let i = 0; i < rawLines.length; i++) {
    const text = rawLines[i];
    if (!text?.trim()) continue;
    let parsed;
    try {
      parsed = JSON.parse(text);
    } catch (error) {
      throw new Error(
        `${path}:${i + 1}: malformed JSON \u2014 ${error instanceof Error ? error.message : String(error)}`
      );
    }
    assertRolloutLine(parsed, `${path}:${i + 1}`);
    lines.push(parsed);
  }
  return lines;
}

// src/rollout/release/card.ts
var RELEASE_FORMATS = ["sft", "verifiers", "rft", "raw"];
var FORMAT_FILES = {
  sft: "sft/train.jsonl",
  verifiers: "verifiers/train.jsonl",
  rft: "rft/train.jsonl",
  raw: "raw/train.jsonl"
};
var FORMAT_DESCRIPTIONS = {
  sft: "Successful trainable-split transcripts (`reward >= 1`, never realness-gated) as `{messages, metadata}` chat JSONL.",
  verifiers: "Prime Intellect verifiers `RolloutOutput`: prompt/completion split at the first assistant turn, plus reward, metrics, tool defs, and token usage.",
  rft: "OpenAI RFT items: prompt turns plus `reference.*` verdict fields for a grader (completions are re-sampled during RFT).",
  raw: `Full \`${ROLLOUT_SCHEMA}\` ledger lines (scrubbed), one per agent invocation.`
};
function unique(values) {
  return [...new Set(values.filter((v) => v !== null))].sort();
}
function formatReward(reward) {
  if (reward === null) return "null";
  return Number.isInteger(reward) ? String(reward) : reward.toFixed(4);
}
function markdownTable(header, rows) {
  return [
    `| ${header.join(" | ")} |`,
    `| ${header.map(() => "---").join(" | ")} |`,
    ...rows.map((row) => `| ${row.join(" | ")} |`)
  ].join("\n");
}
function roleRewardRows(lines) {
  const counts = /* @__PURE__ */ new Map();
  for (const line of lines) {
    const byReward = counts.get(line.role) ?? /* @__PURE__ */ new Map();
    const key = formatReward(line.outcome.reward);
    byReward.set(key, (byReward.get(key) ?? 0) + 1);
    counts.set(line.role, byReward);
  }
  const rows = [];
  for (const role of ROLLOUT_ROLES) {
    const byReward = counts.get(role);
    if (!byReward) continue;
    const keys = [...byReward.keys()].sort((a, b) => {
      if (a === "null") return 1;
      if (b === "null") return -1;
      return Number(b) - Number(a);
    });
    for (const key of keys) rows.push([role, key, String(byReward.get(key))]);
  }
  return rows;
}
function buildDatasetCard(inputs) {
  const { lines, formats, includeProposers, sourceFiles, scrubTotals, excluded, formatCounts } = inputs;
  const runIds = unique(lines.map((line) => line.run_id));
  const generations = [...new Set(lines.map((line) => line.generation))].filter((g) => g !== null).sort((a, b) => a - b);
  const models = unique(lines.map((line) => line.policy.model));
  const harnesses = unique(lines.map((line) => line.policy.harness));
  const captures = unique(lines.map((line) => line.provenance.capture));
  const rewardSources = unique(lines.map((line) => line.outcome.reward_source));
  const gapLines = lines.filter((line) => line.messages.length === 0).length;
  const configs = formats.map(
    (format) => [
      `  - config_name: ${format}`,
      "    data_files:",
      "      - split: train",
      `        path: ${FORMAT_FILES[format]}`
    ].join("\n")
  ).join("\n");
  const frontmatter = [
    "---",
    "license: unknown",
    "pretty_name: Tangle rollout ledger \u2014 agent trajectories",
    "configs:",
    configs,
    "---"
  ].join("\n");
  const formatsTable = markdownTable(
    ["config", "path", "rows", "contents"],
    formats.map((format) => [
      format,
      `\`${FORMAT_FILES[format]}\``,
      String(formatCounts[format] ?? 0),
      FORMAT_DESCRIPTIONS[format]
    ])
  );
  const countsTable = markdownTable(["role", "reward", "lines"], roleRewardRows(lines));
  const scrubTable = markdownTable(
    ["rule", "rewrites"],
    Object.entries(scrubTotals).map(([rule, count]) => [rule, String(count)])
  );
  const proposerNote = includeProposers ? "Proposer sessions are INCLUDED (`--include-proposers`); their transcripts contain improvement-loop harness source." : `Proposer sessions are excluded by default (${excluded.proposers} lines dropped); they contain improvement-loop harness source. Rebuild with \`--include-proposers\` to keep them.`;
  return `${frontmatter}

# Tangle rollout ledger \u2014 agent trajectories

One line per agent invocation (supervisor episode, worker session, proposer shot, judge call, analyst pass) captured by the \`${ROLLOUT_SCHEMA}\` rollout ledger, labeled with improvement-loop coordinates and the official-judge reward, with the full message transcript inline.

This release contains the **trainable split only** (\`search\`, plus the legacy \`train\` alias). Holdout, dev, and canary splits are structurally excluded at build time (never exported), and the build additionally drops any non-trainable line as a fail-closed filter (${excluded.nonTrain} dropped here).

## Formats

${formatsTable}

## Schema (\`${ROLLOUT_SCHEMA}\`)

Each raw line carries:

- \`rollout_id\` / \`parent_rollout_id\` \u2014 invocation identity; workers point at their spawning supervisor episode.
- \`run_id\`, \`experiment_id\`, \`candidate_id\` \u2014 run/experiment/candidate identity from the producing RunRecord, when present.
- \`generation\`, \`candidate_index\` \u2014 improvement-loop coordinates (\`-1\` = baseline campaign; \`null\` = not an improvement loop).
- \`role\` \u2014 one of ${ROLLOUT_ROLES.map((role) => `\`${role}\``).join(", ")}.
- \`task\` \u2014 suite, instance id, split, seed, replicate index.
- \`policy\` \u2014 harness, model, provider, profile commit, prompt/config hashes, sampling params.
- \`messages\` / \`tool_defs\` \u2014 full transcript in canonical OpenAI chat-with-tools form (including \`reasoning_content\`). An empty \`messages\` array is a labeled gap line; \`provenance.gap\` says why the transcript could not be recovered (${gapLines} gap lines in this release).
- \`outcome\` \u2014 \`reward\` (the single scalar), \`reward_source\`, the verbatim judge \`verdict\`, non-scalar \`metrics\`, and \`realness_gated\` (anti-Goodhart flag: reward forced to 0, never SFT-eligible).
- \`cost\` \u2014 usd, token counts, wall time.
- \`artifacts\` / \`provenance\` \u2014 patch/run-dir/transcript pointers (scrubbed) and capture metadata.

## Provenance

- Source ledgers: ${sourceFiles.map((file) => `\`${file}\``).join(", ")}
- Run ids: ${runIds.map((id) => `\`${id}\``).join(", ")}
- Generations: ${generations.join(", ")} (\`-1\` = baseline campaign)
- Models: ${models.map((m) => `\`${m}\``).join(", ")}
- Harnesses: ${harnesses.map((h) => `\`${h}\``).join(", ")}
- Capture modes: ${captures.join(", ")}
- Every reward traces to a named source (reward sources in this release: ${rewardSources.map((s) => `\`${s}\``).join(", ")}).

${proposerNote}

## Reward semantics per role

- **agent** \u2014 the producing RunRecord's holdout/search score, with the realness gate forcing gamed successes to 0.
- **supervisor** \u2014 the official-judge verdict on the episode's delivered artifact (1 = resolved, 0 = not).
- **worker** \u2014 INHERITED from the parent supervisor episode (\`\u2026/inherited\`). Caveat: reward 1 does not establish this worker's individual contribution (sibling workers in the same episode share the episode outcome), and reward 0 does not prove this worker failed.
- **proposer** \u2014 the fraction of improvement-set instances the proposed candidate resolved (\`\u2026/candidate-resolved-fraction\`); a scalar in [0, 1], not a binary verdict.
- **judge / analyst** \u2014 carry the episode verdict where one applies; otherwise \`reward: null\` (a labeled gap, never 0).

## Counts

${countsTable}

Total lines: ${lines.length}

## Scrubbing

Absolute home paths were rewritten to \`$WORK\`, credential-shaped strings were replaced with \`[REDACTED:<kind>]\` markers, internal hostnames were normalized to \`*.internal.example\`, and username-bearing incidentals (\`ls -l\` owner columns, per-user pytest tmpdirs) were normalized to \`user\` / \`$USER\`. Rewrite counts for this release (full per-file breakdown in \`scrub-report.json\`):

${scrubTable}

## License

\`license: unknown\` is a placeholder \u2014 the releasing operator must set the real SPDX license id in the frontmatter above before publishing.

## Citation

\`\`\`bibtex
@misc{tangle_rollout_ledger,
  title = {Tangle rollout ledger \u2014 agent trajectories},
  author = {{Tangle Network}},
  howpublished = {HuggingFace Datasets},
  note = {Operator: fill in the repository URL, authors, and year before publishing}
}
\`\`\`
`;
}

// src/rollout/release/scrub.ts
var SCRUB_RULES = [
  {
    // /home/<user> and /Users/<user> prefixes → $WORK ($HOME-derived paths).
    name: "home-path",
    pattern: /\/(?:home|Users)\/[A-Za-z0-9._-]+/g,
    rewrite: () => "$WORK"
  },
  {
    // Harness stores encode cwd as a dashed path segment (-home-drew-…);
    // strip the leading -home-<user> so the username never ships.
    name: "home-path-encoded",
    pattern: /(?<=\/)-(?:home|Users)-[A-Za-z0-9_.]+/g,
    rewrite: () => "$WORK"
  },
  {
    // pytest names its tmpdir after the invoking user (/tmp/pytest-of-drew).
    name: "tmp-user-dir",
    pattern: /\/tmp\/pytest-of-[A-Za-z0-9._-]+/g,
    rewrite: () => "/tmp/pytest-of-$USER"
  },
  {
    // `ls -l` owner/group columns leak the username in captured tool output.
    // The `(?!user user…)` guard keeps a second pass at zero rewrites.
    name: "ls-owner",
    pattern: /([-bcdlps][-rwxsStT]{9}[.+@]?\s+\d+\s+)(?!user user(?=\s))[A-Za-z0-9._-]+\s+[A-Za-z0-9._-]+(?=\s)/g,
    rewrite: (_match, prefix) => `${prefix}user user`
  },
  {
    // Env-var-shaped secrets: NAME=value where NAME looks credential-bearing.
    // The (?!\[REDACTED:) guard keeps the rule idempotent on its own output.
    name: "env-secret",
    pattern: /\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIALS?))=(?!\[REDACTED:)("[^"]*"|'[^']*'|[^\s"']+)/g,
    rewrite: (_match, name) => `${name}=[REDACTED:env]`
  },
  {
    name: "bearer-token",
    pattern: /\b(Bearer|Basic)\s+(?!\[REDACTED:)[A-Za-z0-9\-._~+/=]{8,}/g,
    rewrite: (_match, scheme) => `${scheme} [REDACTED:bearer]`
  },
  {
    // Bare provider-prefixed keys (OpenAI/Anthropic sk-, GitHub ghp_/gho_/…,
    // fine-grained PATs, HuggingFace hf_, Slack xox*, AWS AKIA).
    name: "api-key",
    pattern: /\b(?:sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{16,}|github_pat_[A-Za-z0-9_]{20,}|hf_[A-Za-z0-9]{16,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[0-9A-Z]{16})\b/g,
    rewrite: () => "[REDACTED:api-key]"
  },
  {
    // Our infra hostnames → placeholder domain, subdomain preserved
    // (router.tangle.tools → router.internal.example).
    name: "infra-host",
    pattern: /(?<![A-Za-z0-9.-])((?:[A-Za-z0-9-]+\.)*)tangle\.(?:tools|network)(?![A-Za-z0-9-])/g,
    rewrite: (_match, prefix) => `${prefix ?? ""}internal.example`
  },
  {
    // Known workstation hostnames leak through email Message-IDs and fqdn
    // lookups inside captured test output; extend the list as machines join.
    name: "machine-host",
    pattern: /\b[A-Za-z0-9]+-GTR-Pro\b/g,
    rewrite: () => "workstation"
  }
];
function emptyScrubCounts() {
  return Object.fromEntries(SCRUB_RULES.map((rule) => [rule.name, 0]));
}
function addScrubCounts(into, from) {
  for (const [name, count] of Object.entries(from)) into[name] = (into[name] ?? 0) + count;
  return into;
}
function scrubText(text, counts) {
  let out = text;
  for (const rule of SCRUB_RULES) {
    out = out.replace(rule.pattern, (match, ...rest) => {
      counts[rule.name] = (counts[rule.name] ?? 0) + 1;
      return rule.rewrite(match, typeof rest[0] === "string" ? rest[0] : void 0);
    });
  }
  return out;
}
function scrubValue(value, counts) {
  if (typeof value === "string") return scrubText(value, counts);
  if (Array.isArray(value)) return value.map((item) => scrubValue(item, counts));
  if (value !== null && typeof value === "object") {
    const out = {};
    for (const [key, item] of Object.entries(value)) out[key] = scrubValue(item, counts);
    return out;
  }
  return value;
}
function scrubRolloutLine(line, counts) {
  return scrubValue(line, counts);
}
function scrubLines(lines) {
  const counts = emptyScrubCounts();
  return { lines: lines.map((line) => scrubRolloutLine(line, counts)), counts };
}
function defaultRolloutScrubber(text) {
  return scrubText(text, emptyScrubCounts());
}

// src/rollout/release/hf-dataset.ts
import { spawnSync } from "child_process";
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
import { basename, dirname as dirname2, join } from "path";
async function buildHfDataset(inputs, options) {
  if (inputs.length === 0) throw new Error("no input ledgers given");
  if (options.formats.length === 0) throw new Error("no formats selected");
  const report = {
    files: {},
    totals: emptyScrubCounts(),
    excluded: { proposers: 0, nonTrain: 0 }
  };
  const kept = [];
  let read = 0;
  for (const input of inputs) {
    const lines = await readRolloutLedger(input);
    read += lines.length;
    const shippable = lines.filter((line) => {
      if (!isTrainableSplit(line.task.split)) {
        report.excluded.nonTrain += 1;
        return false;
      }
      if (!options.includeProposers && line.role === "proposer") {
        report.excluded.proposers += 1;
        return false;
      }
      return true;
    });
    const scrubbed = scrubLines(shippable);
    report.files[input] = scrubbed.counts;
    addScrubCounts(report.totals, scrubbed.counts);
    kept.push(...scrubbed.lines);
  }
  const formatCounts = {};
  const files = [];
  for (const format of options.formats) {
    const path = join(options.out, FORMAT_FILES[format]);
    await mkdir2(dirname2(path), { recursive: true });
    if (format === "raw") {
      await writeRolloutLedger(path, kept);
      formatCounts.raw = kept.length;
    } else if (format === "sft") {
      const rows = toSftRows(kept);
      await writeFile2(path, toJsonl(rows));
      formatCounts.sft = rows.length;
    } else if (format === "verifiers") {
      const outputs = toVerifiersRolloutOutputs(kept);
      await writeFile2(path, toJsonl(outputs));
      formatCounts.verifiers = outputs.length;
    } else {
      const items = toRftItems(kept);
      await writeFile2(path, toJsonl(items));
      formatCounts.rft = items.length;
    }
    files.push(path);
  }
  const reportPath = join(options.out, "scrub-report.json");
  await writeFile2(reportPath, `${JSON.stringify(report, null, 2)}
`);
  files.push(reportPath);
  const cardPath = join(options.out, "README.md");
  await writeFile2(
    cardPath,
    buildDatasetCard({
      lines: kept,
      formats: options.formats,
      includeProposers: options.includeProposers,
      sourceFiles: inputs.map((input) => basename(input)),
      scrubTotals: report.totals,
      excluded: report.excluded,
      formatCounts
    })
  );
  files.push(cardPath);
  return { inputs, read, kept: kept.length, scrub: report, formatCounts, files };
}
function planPushCommand(repo, outDir) {
  return ["huggingface-cli", "upload", repo, outDir, ".", "--repo-type", "dataset"];
}
function pushDataset(repo, outDir) {
  const found = spawnSync("which", ["huggingface-cli"], { stdio: "ignore" });
  if (found.status !== 0) {
    throw new Error(
      "huggingface-cli not found on PATH \u2014 install huggingface_hub[cli] before --push"
    );
  }
  if (!process.env.HF_TOKEN) {
    throw new Error("HF_TOKEN not present in env \u2014 refusing to push");
  }
  const [command, ...args] = planPushCommand(repo, outDir);
  const run = spawnSync(command, args, { stdio: "inherit" });
  if (run.status !== 0) throw new Error(`huggingface-cli upload exited ${String(run.status)}`);
}
var ROLLOUT_RELEASE_USAGE = "usage: agent-eval rollout-release <ledger.jsonl...> --out <dir> [--formats sft,verifiers,rft,raw] [--include-proposers] [--push <org/name>]";
function parseRolloutReleaseArgs(argv) {
  const args = {
    inputs: [],
    out: "",
    formats: [...RELEASE_FORMATS],
    includeProposers: false,
    push: null
  };
  for (let i = 0; i < argv.length; i++) {
    const arg = argv[i];
    if (arg === "--out") {
      args.out = argv[++i] ?? "";
    } else if (arg === "--formats") {
      const raw = (argv[++i] ?? "").split(",").filter(Boolean);
      for (const format of raw) {
        if (!RELEASE_FORMATS.includes(format)) {
          throw new Error(
            `unknown format "${format}" \u2014 expected one of ${RELEASE_FORMATS.join(",")}`
          );
        }
      }
      args.formats = raw;
    } else if (arg === "--include-proposers") {
      args.includeProposers = true;
    } else if (arg === "--push") {
      args.push = argv[++i] ?? null;
    } else if (arg.startsWith("--")) {
      throw new Error(`unknown flag "${arg}"`);
    } else {
      args.inputs.push(arg);
    }
  }
  if (args.inputs.length === 0 || !args.out) {
    throw new Error(ROLLOUT_RELEASE_USAGE);
  }
  if (args.push !== null && !/^[\w.-]+\/[\w.-]+$/.test(args.push)) {
    throw new Error(`--push expects <org/name>, got "${args.push}"`);
  }
  return args;
}
async function runRolloutReleaseCli(argv) {
  let args;
  try {
    args = parseRolloutReleaseArgs(argv);
  } catch (error) {
    process.stderr.write(`${error instanceof Error ? error.message : String(error)}
`);
    return 2;
  }
  const summary = await buildHfDataset(args.inputs, args);
  process.stdout.write(
    `${JSON.stringify(
      {
        read: summary.read,
        kept: summary.kept,
        formatCounts: summary.formatCounts,
        scrub: summary.scrub
      },
      null,
      2
    )}
`
  );
  process.stdout.write(`dataset \u2192 ${args.out} (${summary.files.length} files)
`);
  if (args.push !== null) {
    pushDataset(args.push, args.out);
    process.stdout.write(`pushed \u2192 ${args.push}
`);
  }
  return 0;
}

export {
  toSftRows,
  toRewardRows,
  toVerifiersRolloutOutput,
  toVerifiersRolloutOutputs,
  toRftItem,
  toRftItems,
  toJsonl,
  writeRolloutLedger,
  appendRolloutLines,
  readRolloutLedger,
  RELEASE_FORMATS,
  FORMAT_FILES,
  buildDatasetCard,
  SCRUB_RULES,
  emptyScrubCounts,
  addScrubCounts,
  scrubText,
  scrubRolloutLine,
  scrubLines,
  defaultRolloutScrubber,
  buildHfDataset,
  planPushCommand,
  pushDataset,
  ROLLOUT_RELEASE_USAGE,
  parseRolloutReleaseArgs,
  runRolloutReleaseCli
};
//# sourceMappingURL=chunk-IPYXE555.js.map