@tangle-network/agent-eval 0.123.7 → 0.124.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -4
- package/dist/analyst/index.d.ts +7 -0
- package/dist/analyst/index.js +4 -4
- package/dist/benchmarks/index.d.ts +2 -0
- package/dist/benchmarks/index.js +4 -4
- package/dist/campaign/index.d.ts +13 -0
- package/dist/campaign/index.js +4 -4
- package/dist/chunk-4Y7AAATF.js +1151 -0
- package/dist/chunk-4Y7AAATF.js.map +1 -0
- package/dist/{chunk-VPDOSN3L.js → chunk-5PVZVCZB.js} +7 -5
- package/dist/chunk-5PVZVCZB.js.map +1 -0
- package/dist/{chunk-WP3GEXKI.js → chunk-A6GT67HT.js} +3 -3
- package/dist/{chunk-MHPEGJHC.js → chunk-DT7OXY3C.js} +2 -2
- package/dist/{chunk-MK3Q6GCL.js → chunk-EQUK3RFS.js} +14 -5
- package/dist/chunk-EQUK3RFS.js.map +1 -0
- package/dist/{chunk-N7ZS6PEF.js → chunk-GID26AN4.js} +2 -2
- package/dist/{chunk-4KO6D2BO.js → chunk-HM6V7F3M.js} +2 -2
- package/dist/chunk-IPYXE555.js +594 -0
- package/dist/chunk-IPYXE555.js.map +1 -0
- package/dist/chunk-MAX3TN3C.js +249 -0
- package/dist/chunk-MAX3TN3C.js.map +1 -0
- package/dist/chunk-MGGFVCJ7.js +288 -0
- package/dist/chunk-MGGFVCJ7.js.map +1 -0
- package/dist/{chunk-KKPPFIDS.js → chunk-PMITBABE.js} +7 -5
- package/dist/{chunk-KKPPFIDS.js.map → chunk-PMITBABE.js.map} +1 -1
- package/dist/{chunk-J3LHTAAB.js → chunk-QOTFXW5L.js} +1 -47
- package/dist/chunk-QOTFXW5L.js.map +1 -0
- package/dist/chunk-R7ZRE2KV.js +138 -0
- package/dist/chunk-R7ZRE2KV.js.map +1 -0
- package/dist/chunk-RZTMDUO7.js +49 -0
- package/dist/chunk-RZTMDUO7.js.map +1 -0
- package/dist/{chunk-QNVBALEX.js → chunk-W5B3ZGP3.js} +3 -3
- package/dist/cli.js +13 -2
- package/dist/cli.js.map +1 -1
- package/dist/contract/index.d.ts +9 -0
- package/dist/contract/index.js +4 -4
- package/dist/index.d.ts +701 -82
- package/dist/index.js +57 -121
- package/dist/index.js.map +1 -1
- package/dist/multishot/index.d.ts +2 -0
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.js +4 -2
- package/dist/pipelines/index.js.map +1 -1
- package/dist/rl.d.ts +5 -0
- package/dist/rl.js +2 -2
- package/dist/rollout/index.d.ts +1053 -0
- package/dist/rollout/index.js +109 -0
- package/dist/rollout/index.js.map +1 -0
- package/dist/supervisor-run/index.d.ts +705 -0
- package/dist/supervisor-run/index.js +52 -0
- package/dist/supervisor-run/index.js.map +1 -0
- package/dist/wire/index.d.ts +3 -0
- package/dist/wire/index.js +2 -2
- package/docs/rollout.md +48 -0
- package/package.json +11 -1
- package/dist/chunk-J3LHTAAB.js.map +0 -1
- package/dist/chunk-MK3Q6GCL.js.map +0 -1
- package/dist/chunk-VPDOSN3L.js.map +0 -1
- /package/dist/{chunk-WP3GEXKI.js.map → chunk-A6GT67HT.js.map} +0 -0
- /package/dist/{chunk-MHPEGJHC.js.map → chunk-DT7OXY3C.js.map} +0 -0
- /package/dist/{chunk-N7ZS6PEF.js.map → chunk-GID26AN4.js.map} +0 -0
- /package/dist/{chunk-4KO6D2BO.js.map → chunk-HM6V7F3M.js.map} +0 -0
- /package/dist/{chunk-QNVBALEX.js.map → chunk-W5B3ZGP3.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rollout/exporters.ts","../src/rollout/ledger.ts","../src/rollout/release/card.ts","../src/rollout/release/scrub.ts","../src/rollout/release/hf-dataset.ts"],"sourcesContent":["/**\n * Pure exporters over `tangle.rollout.v1` lines → the training-data shapes\n * the improvement loops feed:\n * - SFT chat JSONL (clean trainable successes, {messages, metadata})\n * - reward rows (every scored line, success or failure, with steps)\n * - Prime Intellect verifiers RolloutOutput (prompt/completion split + reward)\n * - OpenAI RFT items (prompt turns + verdict reference fields)\n *\n * All exporters are pure functions of the lines — filtering (never train on\n * holdout, reward thresholds, the realness gate) happens HERE, on inline\n * labels, no joins.\n */\n\nimport type { ChatMessage, RolloutLine, RolloutSplit, RolloutStep, ToolDef } from './schema'\nimport { isTrainableSplit } from './schema'\n\n// ---------------------------------------------------------------------------\n// (a) SFT chat JSONL\n// ---------------------------------------------------------------------------\n\nexport interface SftExportOptions {\n /** Export only lines with reward ≥ this (default 1 = clean successes only). */\n minReward?: number\n}\n\nexport interface SftRow {\n messages: ChatMessage[]\n metadata: {\n rollout_id: string\n run_id: string\n candidate_id: string | null\n instance_id: string\n reward: number\n }\n}\n\n/**\n * Supervised fine-tune rows: the completed conversation of each qualifying\n * line. Fail-closed filters: trainable split only (never holdout/canary),\n * reward ≥ minReward, realness-gated lines never qualify, gap lines carry\n * no trainable content.\n */\nexport function toSftRows(lines: RolloutLine[], options: SftExportOptions = {}): SftRow[] {\n const minReward = options.minReward ?? 1\n return lines\n .filter(\n (line) =>\n line.outcome.reward !== null &&\n line.outcome.reward >= minReward &&\n line.outcome.realness_gated !== true &&\n isTrainableSplit(line.task.split) &&\n line.messages.length > 0,\n )\n .map((line) => ({\n messages: line.messages,\n metadata: {\n rollout_id: line.rollout_id,\n run_id: line.run_id,\n candidate_id: line.candidate_id ?? null,\n instance_id: line.task.instance_id,\n reward: line.outcome.reward as number,\n },\n }))\n}\n\n// ---------------------------------------------------------------------------\n// (b) Reward rows — every scored line, failures included as signal.\n// ---------------------------------------------------------------------------\n\nexport interface RewardRow {\n /** First user turn — the task prompt. */\n prompt: string\n steps: RolloutStep[]\n reward: number\n metadata: {\n rollout_id: string\n run_id: string\n candidate_id: string | null\n instance_id: string\n split: RolloutSplit\n }\n}\n\n/**\n * Reward-labeled rows: every line with a scalar reward, success or\n * failure. Failures are signal here — only the realness-gate zeroing\n * (applied at mint time) touches the reward, never filtering. Lines with\n * no verdict (reward null) are excluded: an unlabeled example is a gap,\n * not a zero.\n */\nexport function toRewardRows(lines: RolloutLine[]): RewardRow[] {\n return lines\n .filter((line) => line.outcome.reward !== null)\n .map((line) => ({\n prompt: line.messages.find((m) => m.role === 'user')?.content ?? '',\n steps: line.steps ?? [],\n reward: line.outcome.reward as number,\n metadata: {\n rollout_id: line.rollout_id,\n run_id: line.run_id,\n candidate_id: line.candidate_id ?? null,\n instance_id: line.task.instance_id,\n split: line.task.split,\n },\n }))\n}\n\n// ---------------------------------------------------------------------------\n// (c) Prime Intellect verifiers RolloutOutput\n// ---------------------------------------------------------------------------\n\nexport interface VerifiersTokenUsage {\n input_tokens: number | null\n output_tokens: number | null\n reasoning_tokens: number | null\n cache_read_tokens: number | null\n cache_write_tokens: number | null\n}\n\nexport interface VerifiersRolloutOutput {\n /** Messages through the last turn BEFORE the first assistant turn. */\n prompt: ChatMessage[]\n /** The first assistant turn onward — what the policy produced. */\n completion: ChatMessage[]\n reward: number | null\n metrics: Record<string, unknown>\n tool_defs: ToolDef[]\n token_usage: VerifiersTokenUsage\n info: {\n task: RolloutLine['task']\n policy: RolloutLine['policy']\n rollout_id: string\n run_id: string\n experiment_id: string | null\n candidate_id: string | null\n generation: number | null\n candidate_index: number | null\n role: RolloutLine['role']\n }\n}\n\n/** Index of the first assistant turn; messages.length when none exists. */\nfunction firstAssistantIndex(messages: ChatMessage[]): number {\n const index = messages.findIndex((m) => m.role === 'assistant')\n return index === -1 ? messages.length : index\n}\n\nexport function toVerifiersRolloutOutput(line: RolloutLine): VerifiersRolloutOutput {\n const split = firstAssistantIndex(line.messages)\n return {\n prompt: line.messages.slice(0, split),\n completion: line.messages.slice(split),\n reward: line.outcome.reward,\n metrics: line.outcome.metrics,\n tool_defs: line.tool_defs,\n token_usage: {\n input_tokens: line.cost.tokens_in,\n output_tokens: line.cost.tokens_out,\n reasoning_tokens: line.cost.tokens_reasoning,\n cache_read_tokens: line.cost.cache_read,\n cache_write_tokens: line.cost.cache_write,\n },\n info: {\n task: line.task,\n policy: line.policy,\n rollout_id: line.rollout_id,\n run_id: line.run_id,\n experiment_id: line.experiment_id ?? null,\n candidate_id: line.candidate_id ?? null,\n generation: line.generation,\n candidate_index: line.candidate_index,\n role: line.role,\n },\n }\n}\n\nexport function toVerifiersRolloutOutputs(lines: RolloutLine[]): VerifiersRolloutOutput[] {\n return lines.filter((line) => line.messages.length > 0).map(toVerifiersRolloutOutput)\n}\n\n// ---------------------------------------------------------------------------\n// (d) OpenAI RFT items\n// ---------------------------------------------------------------------------\n\nexport interface RftItem {\n /** Prompt turns only — the graded completion is re-sampled during RFT. */\n messages: ChatMessage[]\n /** Verdict/label fields the grader references as item.reference.* */\n reference: {\n reward: number | null\n reward_source: string | null\n verdict: unknown\n instance_id: string\n suite: string\n split: RolloutSplit\n rollout_id: string\n }\n}\n\nexport function toRftItem(line: RolloutLine): RftItem {\n const split = firstAssistantIndex(line.messages)\n return {\n messages: line.messages.slice(0, split),\n reference: {\n reward: line.outcome.reward,\n reward_source: line.outcome.reward_source,\n verdict: line.outcome.verdict,\n instance_id: line.task.instance_id,\n suite: line.task.suite,\n split: line.task.split,\n rollout_id: line.rollout_id,\n },\n }\n}\n\n/** RFT needs a real prompt: lines whose transcript starts with prompt turns. */\nexport function toRftItems(lines: RolloutLine[]): RftItem[] {\n return lines\n .filter((line) => line.messages.length > 0 && firstAssistantIndex(line.messages) > 0)\n .map(toRftItem)\n}\n\n// ---------------------------------------------------------------------------\n// Serialization — one JSON object per line, the interchange format for\n// every export. `tangle.rollout.v1` lines and export rows alike.\n// ---------------------------------------------------------------------------\n\nexport function toJsonl(rows: ReadonlyArray<unknown>): string {\n return rows.map((r) => JSON.stringify(r)).join('\\n') + (rows.length ? '\\n' : '')\n}\n","/**\n * Rollout-ledger file API — append-only JSONL of validated `tangle.rollout.v1`\n * lines. Writes validate BEFORE touching disk (a bad line never lands);\n * reads validate line-by-line and fail loud with the line number, because a\n * silently-skipped rollout is a corrupted dataset.\n */\n\nimport { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname } from 'node:path'\nimport { assertRolloutLine, type RolloutLine } from './schema'\n\nfunction serialize(lines: RolloutLine[]): string {\n for (const [i, line] of lines.entries()) assertRolloutLine(line, `rollout line [${i}]`)\n return lines.map((line) => JSON.stringify(line)).join('\\n') + (lines.length > 0 ? '\\n' : '')\n}\n\n/** Replace the ledger file with exactly `lines`. */\nexport async function writeRolloutLedger(path: string, lines: RolloutLine[]): Promise<void> {\n const payload = serialize(lines)\n await mkdir(dirname(path), { recursive: true })\n await writeFile(path, payload)\n}\n\n/** Append `lines` to the ledger file (created if absent). */\nexport async function appendRolloutLines(path: string, lines: RolloutLine[]): Promise<void> {\n if (lines.length === 0) return\n const payload = serialize(lines)\n await mkdir(dirname(path), { recursive: true })\n await appendFile(path, payload)\n}\n\n/**\n * Read and validate every line. Throws on the first malformed/invalid line\n * (with its 1-based line number) — fail-closed, never a silent drop.\n */\nexport async function readRolloutLedger(path: string): Promise<RolloutLine[]> {\n const raw = await readFile(path, 'utf8')\n const lines: RolloutLine[] = []\n const rawLines = raw.split('\\n')\n for (let i = 0; i < rawLines.length; i++) {\n const text = rawLines[i]\n if (!text?.trim()) continue\n let parsed: unknown\n try {\n parsed = JSON.parse(text)\n } catch (error) {\n throw new Error(\n `${path}:${i + 1}: malformed JSON — ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n assertRolloutLine(parsed, `${path}:${i + 1}`)\n lines.push(parsed)\n }\n return lines\n}\n","/**\n * HuggingFace dataset-card (README.md) generation for a rollout-ledger release.\n *\n * The card is a pure function of the SCRUBBED lines plus the release options —\n * no timestamps, no environment reads — so rebuilding from the same ledger\n * yields byte-identical output. It documents the schema, provenance (run ids,\n * generations, the official judge), per-role reward semantics including the\n * inherited/contribution caveat, and a role × reward counts table.\n */\n\nimport { ROLLOUT_ROLES, ROLLOUT_SCHEMA, type RolloutLine, type RolloutRole } from '../schema'\nimport type { ScrubCounts } from './scrub'\n\nexport const RELEASE_FORMATS = ['sft', 'verifiers', 'rft', 'raw'] as const\nexport type ReleaseFormat = (typeof RELEASE_FORMATS)[number]\n\n/** Format → data file path inside the dataset dir (train split only). */\nexport const FORMAT_FILES: Record<ReleaseFormat, string> = {\n sft: 'sft/train.jsonl',\n verifiers: 'verifiers/train.jsonl',\n rft: 'rft/train.jsonl',\n raw: 'raw/train.jsonl',\n}\n\nconst FORMAT_DESCRIPTIONS: Record<ReleaseFormat, string> = {\n sft: 'Successful trainable-split transcripts (`reward >= 1`, never realness-gated) as `{messages, metadata}` chat JSONL.',\n verifiers:\n 'Prime Intellect verifiers `RolloutOutput`: prompt/completion split at the first assistant turn, plus reward, metrics, tool defs, and token usage.',\n rft: 'OpenAI RFT items: prompt turns plus `reference.*` verdict fields for a grader (completions are re-sampled during RFT).',\n raw: `Full \\`${ROLLOUT_SCHEMA}\\` ledger lines (scrubbed), one per agent invocation.`,\n}\n\nexport interface DatasetCardInputs {\n /** Scrubbed, release-filtered lines (what actually ships). */\n lines: RolloutLine[]\n formats: ReleaseFormat[]\n includeProposers: boolean\n /** Source ledger basenames, for provenance. */\n sourceFiles: string[]\n scrubTotals: ScrubCounts\n excluded: { proposers: number; nonTrain: number }\n formatCounts: Partial<Record<ReleaseFormat, number>>\n}\n\nfunction unique(values: Array<string | null>): string[] {\n return [...new Set(values.filter((v): v is string => v !== null))].sort()\n}\n\nfunction formatReward(reward: number | null): string {\n if (reward === null) return 'null'\n return Number.isInteger(reward) ? String(reward) : reward.toFixed(4)\n}\n\nfunction markdownTable(header: string[], rows: string[][]): string {\n return [\n `| ${header.join(' | ')} |`,\n `| ${header.map(() => '---').join(' | ')} |`,\n ...rows.map((row) => `| ${row.join(' | ')} |`),\n ].join('\\n')\n}\n\nfunction roleRewardRows(lines: RolloutLine[]): string[][] {\n const counts = new Map<RolloutRole, Map<string, number>>()\n for (const line of lines) {\n const byReward = counts.get(line.role) ?? new Map<string, number>()\n const key = formatReward(line.outcome.reward)\n byReward.set(key, (byReward.get(key) ?? 0) + 1)\n counts.set(line.role, byReward)\n }\n const rows: string[][] = []\n for (const role of ROLLOUT_ROLES) {\n const byReward = counts.get(role)\n if (!byReward) continue\n const keys = [...byReward.keys()].sort((a, b) => {\n if (a === 'null') return 1\n if (b === 'null') return -1\n return Number(b) - Number(a)\n })\n for (const key of keys) rows.push([role, key, String(byReward.get(key))])\n }\n return rows\n}\n\nexport function buildDatasetCard(inputs: DatasetCardInputs): string {\n const { lines, formats, includeProposers, sourceFiles, scrubTotals, excluded, formatCounts } =\n inputs\n\n const runIds = unique(lines.map((line) => line.run_id))\n const generations = [...new Set(lines.map((line) => line.generation))]\n .filter((g): g is number => g !== null)\n .sort((a, b) => a - b)\n const models = unique(lines.map((line) => line.policy.model))\n const harnesses = unique(lines.map((line) => line.policy.harness))\n const captures = unique(lines.map((line) => line.provenance.capture))\n const rewardSources = unique(lines.map((line) => line.outcome.reward_source))\n const gapLines = lines.filter((line) => line.messages.length === 0).length\n\n const configs = formats\n .map((format) =>\n [\n ` - config_name: ${format}`,\n ' data_files:',\n ' - split: train',\n ` path: ${FORMAT_FILES[format]}`,\n ].join('\\n'),\n )\n .join('\\n')\n\n const frontmatter = [\n '---',\n 'license: unknown',\n 'pretty_name: Tangle rollout ledger — agent trajectories',\n 'configs:',\n configs,\n '---',\n ].join('\\n')\n\n const formatsTable = markdownTable(\n ['config', 'path', 'rows', 'contents'],\n formats.map((format) => [\n format,\n `\\`${FORMAT_FILES[format]}\\``,\n String(formatCounts[format] ?? 0),\n FORMAT_DESCRIPTIONS[format],\n ]),\n )\n\n const countsTable = markdownTable(['role', 'reward', 'lines'], roleRewardRows(lines))\n\n const scrubTable = markdownTable(\n ['rule', 'rewrites'],\n Object.entries(scrubTotals).map(([rule, count]) => [rule, String(count)]),\n )\n\n const proposerNote = includeProposers\n ? 'Proposer sessions are INCLUDED (`--include-proposers`); their transcripts contain improvement-loop harness source.'\n : `Proposer sessions are excluded by default (${excluded.proposers} lines dropped); they contain improvement-loop harness source. Rebuild with \\`--include-proposers\\` to keep them.`\n\n return `${frontmatter}\n\n# Tangle rollout ledger — agent trajectories\n\nOne 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.\n\nThis 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).\n\n## Formats\n\n${formatsTable}\n\n## Schema (\\`${ROLLOUT_SCHEMA}\\`)\n\nEach raw line carries:\n\n- \\`rollout_id\\` / \\`parent_rollout_id\\` — invocation identity; workers point at their spawning supervisor episode.\n- \\`run_id\\`, \\`experiment_id\\`, \\`candidate_id\\` — run/experiment/candidate identity from the producing RunRecord, when present.\n- \\`generation\\`, \\`candidate_index\\` — improvement-loop coordinates (\\`-1\\` = baseline campaign; \\`null\\` = not an improvement loop).\n- \\`role\\` — one of ${ROLLOUT_ROLES.map((role) => `\\`${role}\\``).join(', ')}.\n- \\`task\\` — suite, instance id, split, seed, replicate index.\n- \\`policy\\` — harness, model, provider, profile commit, prompt/config hashes, sampling params.\n- \\`messages\\` / \\`tool_defs\\` — 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).\n- \\`outcome\\` — \\`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).\n- \\`cost\\` — usd, token counts, wall time.\n- \\`artifacts\\` / \\`provenance\\` — patch/run-dir/transcript pointers (scrubbed) and capture metadata.\n\n## Provenance\n\n- Source ledgers: ${sourceFiles.map((file) => `\\`${file}\\``).join(', ')}\n- Run ids: ${runIds.map((id) => `\\`${id}\\``).join(', ')}\n- Generations: ${generations.join(', ')} (\\`-1\\` = baseline campaign)\n- Models: ${models.map((m) => `\\`${m}\\``).join(', ')}\n- Harnesses: ${harnesses.map((h) => `\\`${h}\\``).join(', ')}\n- Capture modes: ${captures.join(', ')}\n- Every reward traces to a named source (reward sources in this release: ${rewardSources.map((s) => `\\`${s}\\``).join(', ')}).\n\n${proposerNote}\n\n## Reward semantics per role\n\n- **agent** — the producing RunRecord's holdout/search score, with the realness gate forcing gamed successes to 0.\n- **supervisor** — the official-judge verdict on the episode's delivered artifact (1 = resolved, 0 = not).\n- **worker** — INHERITED from the parent supervisor episode (\\`…/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.\n- **proposer** — the fraction of improvement-set instances the proposed candidate resolved (\\`…/candidate-resolved-fraction\\`); a scalar in [0, 1], not a binary verdict.\n- **judge / analyst** — carry the episode verdict where one applies; otherwise \\`reward: null\\` (a labeled gap, never 0).\n\n## Counts\n\n${countsTable}\n\nTotal lines: ${lines.length}\n\n## Scrubbing\n\nAbsolute 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\\`):\n\n${scrubTable}\n\n## License\n\n\\`license: unknown\\` is a placeholder — the releasing operator must set the real SPDX license id in the frontmatter above before publishing.\n\n## Citation\n\n\\`\\`\\`bibtex\n@misc{tangle_rollout_ledger,\n title = {Tangle rollout ledger — agent trajectories},\n author = {{Tangle Network}},\n howpublished = {HuggingFace Datasets},\n note = {Operator: fill in the repository URL, authors, and year before publishing}\n}\n\\`\\`\\`\n`\n}\n","/**\n * Deterministic scrubbing pass over rollout-ledger lines before public release.\n *\n * Every rule is a pure regex rewrite applied to every string value in a line\n * (messages, artifacts, run ids, tool arguments — everywhere), so the scrubbed\n * line is still a valid `tangle.rollout.v1` line. Rules are idempotent:\n * scrub(scrub(x)) === scrub(x), and a second pass counts zero hits — that is\n * the property the release pipeline relies on to prove nothing half-scrubbed\n * ships. Rule order matters: whole `KEY=value` env pairs are redacted before\n * the bare-key rule so one secret is never counted twice.\n */\n\nimport type { RolloutLine } from '../schema'\n\nexport interface ScrubRule {\n name: string\n pattern: RegExp\n /** Rewrite for one match; `g1` is the first capture group when present. */\n rewrite: (match: string, g1?: string) => string\n}\n\nexport const SCRUB_RULES: readonly ScrubRule[] = [\n {\n // /home/<user> and /Users/<user> prefixes → $WORK ($HOME-derived paths).\n name: 'home-path',\n pattern: /\\/(?:home|Users)\\/[A-Za-z0-9._-]+/g,\n rewrite: () => '$WORK',\n },\n {\n // Harness stores encode cwd as a dashed path segment (-home-drew-…);\n // strip the leading -home-<user> so the username never ships.\n name: 'home-path-encoded',\n pattern: /(?<=\\/)-(?:home|Users)-[A-Za-z0-9_.]+/g,\n rewrite: () => '$WORK',\n },\n {\n // pytest names its tmpdir after the invoking user (/tmp/pytest-of-drew).\n name: 'tmp-user-dir',\n pattern: /\\/tmp\\/pytest-of-[A-Za-z0-9._-]+/g,\n rewrite: () => '/tmp/pytest-of-$USER',\n },\n {\n // `ls -l` owner/group columns leak the username in captured tool output.\n // The `(?!user user…)` guard keeps a second pass at zero rewrites.\n name: 'ls-owner',\n pattern:\n /([-bcdlps][-rwxsStT]{9}[.+@]?\\s+\\d+\\s+)(?!user user(?=\\s))[A-Za-z0-9._-]+\\s+[A-Za-z0-9._-]+(?=\\s)/g,\n rewrite: (_match, prefix) => `${prefix}user user`,\n },\n {\n // Env-var-shaped secrets: NAME=value where NAME looks credential-bearing.\n // The (?!\\[REDACTED:) guard keeps the rule idempotent on its own output.\n name: 'env-secret',\n pattern:\n /\\b([A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIALS?))=(?!\\[REDACTED:)(\"[^\"]*\"|'[^']*'|[^\\s\"']+)/g,\n rewrite: (_match, name) => `${name}=[REDACTED:env]`,\n },\n {\n name: 'bearer-token',\n pattern: /\\b(Bearer|Basic)\\s+(?!\\[REDACTED:)[A-Za-z0-9\\-._~+/=]{8,}/g,\n rewrite: (_match, scheme) => `${scheme} [REDACTED:bearer]`,\n },\n {\n // Bare provider-prefixed keys (OpenAI/Anthropic sk-, GitHub ghp_/gho_/…,\n // fine-grained PATs, HuggingFace hf_, Slack xox*, AWS AKIA).\n name: 'api-key',\n pattern:\n /\\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,\n rewrite: () => '[REDACTED:api-key]',\n },\n {\n // Our infra hostnames → placeholder domain, subdomain preserved\n // (router.tangle.tools → router.internal.example).\n name: 'infra-host',\n pattern: /(?<![A-Za-z0-9.-])((?:[A-Za-z0-9-]+\\.)*)tangle\\.(?:tools|network)(?![A-Za-z0-9-])/g,\n rewrite: (_match, prefix) => `${prefix ?? ''}internal.example`,\n },\n {\n // Known workstation hostnames leak through email Message-IDs and fqdn\n // lookups inside captured test output; extend the list as machines join.\n name: 'machine-host',\n pattern: /\\b[A-Za-z0-9]+-GTR-Pro\\b/g,\n rewrite: () => 'workstation',\n },\n]\n\n/** Rule name → number of matches rewritten. Always carries every rule (0 is data). */\nexport type ScrubCounts = Record<string, number>\n\nexport function emptyScrubCounts(): ScrubCounts {\n return Object.fromEntries(SCRUB_RULES.map((rule) => [rule.name, 0]))\n}\n\nexport function addScrubCounts(into: ScrubCounts, from: ScrubCounts): ScrubCounts {\n for (const [name, count] of Object.entries(from)) into[name] = (into[name] ?? 0) + count\n return into\n}\n\nexport function scrubText(text: string, counts: ScrubCounts): string {\n let out = text\n for (const rule of SCRUB_RULES) {\n out = out.replace(rule.pattern, (match: string, ...rest: unknown[]) => {\n counts[rule.name] = (counts[rule.name] ?? 0) + 1\n return rule.rewrite(match, typeof rest[0] === 'string' ? rest[0] : undefined)\n })\n }\n return out\n}\n\nfunction scrubValue(value: unknown, counts: ScrubCounts): unknown {\n if (typeof value === 'string') return scrubText(value, counts)\n if (Array.isArray(value)) return value.map((item) => scrubValue(item, counts))\n if (value !== null && typeof value === 'object') {\n const out: Record<string, unknown> = {}\n for (const [key, item] of Object.entries(value)) out[key] = scrubValue(item, counts)\n return out\n }\n return value\n}\n\n/** Scrub every string value in a line; structure and key order are preserved. */\nexport function scrubRolloutLine(line: RolloutLine, counts: ScrubCounts): RolloutLine {\n return scrubValue(line, counts) as RolloutLine\n}\n\nexport function scrubLines(lines: RolloutLine[]): { lines: RolloutLine[]; counts: ScrubCounts } {\n const counts = emptyScrubCounts()\n return { lines: lines.map((line) => scrubRolloutLine(line, counts)), counts }\n}\n\n/**\n * A `RolloutScrubber` (text → text) applying the full rule set — the\n * default hook to pass to `mintRolloutRows({ scrub })` so lines are\n * scrubbed at mint time, before they ever reach a ledger file. Release\n * builds re-run `scrubLines` regardless (idempotent), so double-scrubbing\n * is safe and counted as zero.\n */\nexport function defaultRolloutScrubber(text: string): string {\n return scrubText(text, emptyScrubCounts())\n}\n","/**\n * One-command HuggingFace dataset release from rollout ledgers:\n *\n * agent-eval rollout-release <ledger.jsonl...> --out <dir> \\\n * [--formats sft,verifiers,rft,raw] [--include-proposers] [--push <org/name>]\n *\n * Pipeline per input ledger: read + validate → fail-closed filters\n * (trainable split only; proposer sessions dropped unless\n * --include-proposers, they contain improvement-loop harness source) →\n * deterministic scrub → export the requested formats + scrub-report.json +\n * auto-generated README.md card. Deterministic: same inputs and flags →\n * byte-identical output dir.\n *\n * --push uploads the built dir with `huggingface-cli upload` only when the\n * CLI exists on PATH and HF_TOKEN is present in the env; the token is\n * never printed. Everything else runs fully offline.\n */\n\nimport { spawnSync } from 'node:child_process'\nimport { mkdir, writeFile } from 'node:fs/promises'\nimport { basename, dirname, join } from 'node:path'\nimport { toJsonl, toRftItems, toSftRows, toVerifiersRolloutOutputs } from '../exporters'\nimport { readRolloutLedger, writeRolloutLedger } from '../ledger'\nimport { isTrainableSplit, type RolloutLine } from '../schema'\nimport { buildDatasetCard, FORMAT_FILES, RELEASE_FORMATS, type ReleaseFormat } from './card'\nimport { addScrubCounts, emptyScrubCounts, type ScrubCounts, scrubLines } from './scrub'\n\nexport interface BuildOptions {\n out: string\n formats: ReleaseFormat[]\n includeProposers: boolean\n}\n\nexport interface ScrubReport {\n /** Input ledger path → rule → rewrite count (only shipped lines are scrubbed). */\n files: Record<string, ScrubCounts>\n totals: ScrubCounts\n excluded: { proposers: number; nonTrain: number }\n}\n\nexport interface BuildSummary {\n inputs: string[]\n read: number\n kept: number\n scrub: ScrubReport\n formatCounts: Partial<Record<ReleaseFormat, number>>\n files: string[]\n}\n\nexport async function buildHfDataset(\n inputs: string[],\n options: BuildOptions,\n): Promise<BuildSummary> {\n if (inputs.length === 0) throw new Error('no input ledgers given')\n if (options.formats.length === 0) throw new Error('no formats selected')\n\n const report: ScrubReport = {\n files: {},\n totals: emptyScrubCounts(),\n excluded: { proposers: 0, nonTrain: 0 },\n }\n const kept: RolloutLine[] = []\n let read = 0\n\n for (const input of inputs) {\n const lines = await readRolloutLedger(input)\n read += lines.length\n const shippable = lines.filter((line) => {\n if (!isTrainableSplit(line.task.split)) {\n report.excluded.nonTrain += 1\n return false\n }\n if (!options.includeProposers && line.role === 'proposer') {\n report.excluded.proposers += 1\n return false\n }\n return true\n })\n const scrubbed = scrubLines(shippable)\n report.files[input] = scrubbed.counts\n addScrubCounts(report.totals, scrubbed.counts)\n kept.push(...scrubbed.lines)\n }\n\n const formatCounts: Partial<Record<ReleaseFormat, number>> = {}\n const files: string[] = []\n\n for (const format of options.formats) {\n const path = join(options.out, FORMAT_FILES[format])\n await mkdir(dirname(path), { recursive: true })\n if (format === 'raw') {\n // writeRolloutLedger re-validates every scrubbed line before it lands.\n await writeRolloutLedger(path, kept)\n formatCounts.raw = kept.length\n } else if (format === 'sft') {\n const rows = toSftRows(kept)\n await writeFile(path, toJsonl(rows))\n formatCounts.sft = rows.length\n } else if (format === 'verifiers') {\n const outputs = toVerifiersRolloutOutputs(kept)\n await writeFile(path, toJsonl(outputs))\n formatCounts.verifiers = outputs.length\n } else {\n const items = toRftItems(kept)\n await writeFile(path, toJsonl(items))\n formatCounts.rft = items.length\n }\n files.push(path)\n }\n\n const reportPath = join(options.out, 'scrub-report.json')\n await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\\n`)\n files.push(reportPath)\n\n const cardPath = join(options.out, 'README.md')\n await writeFile(\n cardPath,\n buildDatasetCard({\n lines: kept,\n formats: options.formats,\n includeProposers: options.includeProposers,\n sourceFiles: inputs.map((input) => basename(input)),\n scrubTotals: report.totals,\n excluded: report.excluded,\n formatCounts,\n }),\n )\n files.push(cardPath)\n\n return { inputs, read, kept: kept.length, scrub: report, formatCounts, files }\n}\n\nexport function planPushCommand(repo: string, outDir: string): string[] {\n return ['huggingface-cli', 'upload', repo, outDir, '.', '--repo-type', 'dataset']\n}\n\nexport function pushDataset(repo: string, outDir: string): void {\n const found = spawnSync('which', ['huggingface-cli'], { stdio: 'ignore' })\n if (found.status !== 0) {\n throw new Error(\n 'huggingface-cli not found on PATH — install huggingface_hub[cli] before --push',\n )\n }\n if (!process.env.HF_TOKEN) {\n throw new Error('HF_TOKEN not present in env — refusing to push')\n }\n const [command, ...args] = planPushCommand(repo, outDir) as [string, ...string[]]\n // Token stays in the inherited env; it is never echoed or interpolated.\n const run = spawnSync(command, args, { stdio: 'inherit' })\n if (run.status !== 0) throw new Error(`huggingface-cli upload exited ${String(run.status)}`)\n}\n\nexport interface RolloutReleaseCliArgs extends BuildOptions {\n inputs: string[]\n push: string | null\n}\n\nexport const ROLLOUT_RELEASE_USAGE =\n 'usage: agent-eval rollout-release <ledger.jsonl...> --out <dir> [--formats sft,verifiers,rft,raw] [--include-proposers] [--push <org/name>]'\n\nexport function parseRolloutReleaseArgs(argv: string[]): RolloutReleaseCliArgs {\n const args: RolloutReleaseCliArgs = {\n inputs: [],\n out: '',\n formats: [...RELEASE_FORMATS],\n includeProposers: false,\n push: null,\n }\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i]!\n if (arg === '--out') {\n args.out = argv[++i] ?? ''\n } else if (arg === '--formats') {\n const raw = (argv[++i] ?? '').split(',').filter(Boolean)\n for (const format of raw) {\n if (!RELEASE_FORMATS.includes(format as ReleaseFormat)) {\n throw new Error(\n `unknown format \"${format}\" — expected one of ${RELEASE_FORMATS.join(',')}`,\n )\n }\n }\n args.formats = raw as ReleaseFormat[]\n } else if (arg === '--include-proposers') {\n args.includeProposers = true\n } else if (arg === '--push') {\n args.push = argv[++i] ?? null\n } else if (arg.startsWith('--')) {\n throw new Error(`unknown flag \"${arg}\"`)\n } else {\n args.inputs.push(arg)\n }\n }\n if (args.inputs.length === 0 || !args.out) {\n throw new Error(ROLLOUT_RELEASE_USAGE)\n }\n if (args.push !== null && !/^[\\w.-]+\\/[\\w.-]+$/.test(args.push)) {\n throw new Error(`--push expects <org/name>, got \"${args.push}\"`)\n }\n return args\n}\n\n/** CLI driver for `agent-eval rollout-release`. Returns the process exit code. */\nexport async function runRolloutReleaseCli(argv: string[]): Promise<number> {\n let args: RolloutReleaseCliArgs\n try {\n args = parseRolloutReleaseArgs(argv)\n } catch (error) {\n process.stderr.write(`${error instanceof Error ? error.message : String(error)}\\n`)\n return 2\n }\n const summary = await buildHfDataset(args.inputs, args)\n process.stdout.write(\n `${JSON.stringify(\n {\n read: summary.read,\n kept: summary.kept,\n formatCounts: summary.formatCounts,\n scrub: summary.scrub,\n },\n null,\n 2,\n )}\\n`,\n )\n process.stdout.write(`dataset → ${args.out} (${summary.files.length} files)\\n`)\n if (args.push !== null) {\n pushDataset(args.push, args.out)\n process.stdout.write(`pushed → ${args.push}\\n`)\n }\n return 0\n}\n"],"mappings":";;;;;;;;AA0CO,SAAS,UAAU,OAAsB,UAA4B,CAAC,GAAa;AACxF,QAAM,YAAY,QAAQ,aAAa;AACvC,SAAO,MACJ;AAAA,IACC,CAAC,SACC,KAAK,QAAQ,WAAW,QACxB,KAAK,QAAQ,UAAU,aACvB,KAAK,QAAQ,mBAAmB,QAChC,iBAAiB,KAAK,KAAK,KAAK,KAChC,KAAK,SAAS,SAAS;AAAA,EAC3B,EACC,IAAI,CAAC,UAAU;AAAA,IACd,UAAU,KAAK;AAAA,IACf,UAAU;AAAA,MACR,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb,cAAc,KAAK,gBAAgB;AAAA,MACnC,aAAa,KAAK,KAAK;AAAA,MACvB,QAAQ,KAAK,QAAQ;AAAA,IACvB;AAAA,EACF,EAAE;AACN;AA2BO,SAAS,aAAa,OAAmC;AAC9D,SAAO,MACJ,OAAO,CAAC,SAAS,KAAK,QAAQ,WAAW,IAAI,EAC7C,IAAI,CAAC,UAAU;AAAA,IACd,QAAQ,KAAK,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,GAAG,WAAW;AAAA,IACjE,OAAO,KAAK,SAAS,CAAC;AAAA,IACtB,QAAQ,KAAK,QAAQ;AAAA,IACrB,UAAU;AAAA,MACR,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb,cAAc,KAAK,gBAAgB;AAAA,MACnC,aAAa,KAAK,KAAK;AAAA,MACvB,OAAO,KAAK,KAAK;AAAA,IACnB;AAAA,EACF,EAAE;AACN;AAqCA,SAAS,oBAAoB,UAAiC;AAC5D,QAAM,QAAQ,SAAS,UAAU,CAAC,MAAM,EAAE,SAAS,WAAW;AAC9D,SAAO,UAAU,KAAK,SAAS,SAAS;AAC1C;AAEO,SAAS,yBAAyB,MAA2C;AAClF,QAAM,QAAQ,oBAAoB,KAAK,QAAQ;AAC/C,SAAO;AAAA,IACL,QAAQ,KAAK,SAAS,MAAM,GAAG,KAAK;AAAA,IACpC,YAAY,KAAK,SAAS,MAAM,KAAK;AAAA,IACrC,QAAQ,KAAK,QAAQ;AAAA,IACrB,SAAS,KAAK,QAAQ;AAAA,IACtB,WAAW,KAAK;AAAA,IAChB,aAAa;AAAA,MACX,cAAc,KAAK,KAAK;AAAA,MACxB,eAAe,KAAK,KAAK;AAAA,MACzB,kBAAkB,KAAK,KAAK;AAAA,MAC5B,mBAAmB,KAAK,KAAK;AAAA,MAC7B,oBAAoB,KAAK,KAAK;AAAA,IAChC;AAAA,IACA,MAAM;AAAA,MACJ,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,YAAY,KAAK;AAAA,MACjB,QAAQ,KAAK;AAAA,MACb,eAAe,KAAK,iBAAiB;AAAA,MACrC,cAAc,KAAK,gBAAgB;AAAA,MACnC,YAAY,KAAK;AAAA,MACjB,iBAAiB,KAAK;AAAA,MACtB,MAAM,KAAK;AAAA,IACb;AAAA,EACF;AACF;AAEO,SAAS,0BAA0B,OAAgD;AACxF,SAAO,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,SAAS,CAAC,EAAE,IAAI,wBAAwB;AACtF;AAqBO,SAAS,UAAU,MAA4B;AACpD,QAAM,QAAQ,oBAAoB,KAAK,QAAQ;AAC/C,SAAO;AAAA,IACL,UAAU,KAAK,SAAS,MAAM,GAAG,KAAK;AAAA,IACtC,WAAW;AAAA,MACT,QAAQ,KAAK,QAAQ;AAAA,MACrB,eAAe,KAAK,QAAQ;AAAA,MAC5B,SAAS,KAAK,QAAQ;AAAA,MACtB,aAAa,KAAK,KAAK;AAAA,MACvB,OAAO,KAAK,KAAK;AAAA,MACjB,OAAO,KAAK,KAAK;AAAA,MACjB,YAAY,KAAK;AAAA,IACnB;AAAA,EACF;AACF;AAGO,SAAS,WAAW,OAAiC;AAC1D,SAAO,MACJ,OAAO,CAAC,SAAS,KAAK,SAAS,SAAS,KAAK,oBAAoB,KAAK,QAAQ,IAAI,CAAC,EACnF,IAAI,SAAS;AAClB;AAOO,SAAS,QAAQ,MAAsC;AAC5D,SAAO,KAAK,IAAI,CAAC,MAAM,KAAK,UAAU,CAAC,CAAC,EAAE,KAAK,IAAI,KAAK,KAAK,SAAS,OAAO;AAC/E;;;AC9NA,SAAS,YAAY,OAAO,UAAU,iBAAiB;AACvD,SAAS,eAAe;AAGxB,SAAS,UAAU,OAA8B;AAC/C,aAAW,CAAC,GAAG,IAAI,KAAK,MAAM,QAAQ,EAAG,mBAAkB,MAAM,iBAAiB,CAAC,GAAG;AACtF,SAAO,MAAM,IAAI,CAAC,SAAS,KAAK,UAAU,IAAI,CAAC,EAAE,KAAK,IAAI,KAAK,MAAM,SAAS,IAAI,OAAO;AAC3F;AAGA,eAAsB,mBAAmB,MAAc,OAAqC;AAC1F,QAAM,UAAU,UAAU,KAAK;AAC/B,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAM,UAAU,MAAM,OAAO;AAC/B;AAGA,eAAsB,mBAAmB,MAAc,OAAqC;AAC1F,MAAI,MAAM,WAAW,EAAG;AACxB,QAAM,UAAU,UAAU,KAAK;AAC/B,QAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAM,WAAW,MAAM,OAAO;AAChC;AAMA,eAAsB,kBAAkB,MAAsC;AAC5E,QAAM,MAAM,MAAM,SAAS,MAAM,MAAM;AACvC,QAAM,QAAuB,CAAC;AAC9B,QAAM,WAAW,IAAI,MAAM,IAAI;AAC/B,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,UAAM,OAAO,SAAS,CAAC;AACvB,QAAI,CAAC,MAAM,KAAK,EAAG;AACnB,QAAI;AACJ,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,SAAS,OAAO;AACd,YAAM,IAAI;AAAA,QACR,GAAG,IAAI,IAAI,IAAI,CAAC,2BAAsB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,MAC9F;AAAA,IACF;AACA,sBAAkB,QAAQ,GAAG,IAAI,IAAI,IAAI,CAAC,EAAE;AAC5C,UAAM,KAAK,MAAM;AAAA,EACnB;AACA,SAAO;AACT;;;ACzCO,IAAM,kBAAkB,CAAC,OAAO,aAAa,OAAO,KAAK;AAIzD,IAAM,eAA8C;AAAA,EACzD,KAAK;AAAA,EACL,WAAW;AAAA,EACX,KAAK;AAAA,EACL,KAAK;AACP;AAEA,IAAM,sBAAqD;AAAA,EACzD,KAAK;AAAA,EACL,WACE;AAAA,EACF,KAAK;AAAA,EACL,KAAK,UAAU,cAAc;AAC/B;AAcA,SAAS,OAAO,QAAwC;AACtD,SAAO,CAAC,GAAG,IAAI,IAAI,OAAO,OAAO,CAAC,MAAmB,MAAM,IAAI,CAAC,CAAC,EAAE,KAAK;AAC1E;AAEA,SAAS,aAAa,QAA+B;AACnD,MAAI,WAAW,KAAM,QAAO;AAC5B,SAAO,OAAO,UAAU,MAAM,IAAI,OAAO,MAAM,IAAI,OAAO,QAAQ,CAAC;AACrE;AAEA,SAAS,cAAc,QAAkB,MAA0B;AACjE,SAAO;AAAA,IACL,KAAK,OAAO,KAAK,KAAK,CAAC;AAAA,IACvB,KAAK,OAAO,IAAI,MAAM,KAAK,EAAE,KAAK,KAAK,CAAC;AAAA,IACxC,GAAG,KAAK,IAAI,CAAC,QAAQ,KAAK,IAAI,KAAK,KAAK,CAAC,IAAI;AAAA,EAC/C,EAAE,KAAK,IAAI;AACb;AAEA,SAAS,eAAe,OAAkC;AACxD,QAAM,SAAS,oBAAI,IAAsC;AACzD,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,OAAO,IAAI,KAAK,IAAI,KAAK,oBAAI,IAAoB;AAClE,UAAM,MAAM,aAAa,KAAK,QAAQ,MAAM;AAC5C,aAAS,IAAI,MAAM,SAAS,IAAI,GAAG,KAAK,KAAK,CAAC;AAC9C,WAAO,IAAI,KAAK,MAAM,QAAQ;AAAA,EAChC;AACA,QAAM,OAAmB,CAAC;AAC1B,aAAW,QAAQ,eAAe;AAChC,UAAM,WAAW,OAAO,IAAI,IAAI;AAChC,QAAI,CAAC,SAAU;AACf,UAAM,OAAO,CAAC,GAAG,SAAS,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM;AAC/C,UAAI,MAAM,OAAQ,QAAO;AACzB,UAAI,MAAM,OAAQ,QAAO;AACzB,aAAO,OAAO,CAAC,IAAI,OAAO,CAAC;AAAA,IAC7B,CAAC;AACD,eAAW,OAAO,KAAM,MAAK,KAAK,CAAC,MAAM,KAAK,OAAO,SAAS,IAAI,GAAG,CAAC,CAAC,CAAC;AAAA,EAC1E;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,QAAmC;AAClE,QAAM,EAAE,OAAO,SAAS,kBAAkB,aAAa,aAAa,UAAU,aAAa,IACzF;AAEF,QAAM,SAAS,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,MAAM,CAAC;AACtD,QAAM,cAAc,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,EAClE,OAAO,CAAC,MAAmB,MAAM,IAAI,EACrC,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvB,QAAM,SAAS,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,OAAO,KAAK,CAAC;AAC5D,QAAM,YAAY,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,OAAO,OAAO,CAAC;AACjE,QAAM,WAAW,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,WAAW,OAAO,CAAC;AACpE,QAAM,gBAAgB,OAAO,MAAM,IAAI,CAAC,SAAS,KAAK,QAAQ,aAAa,CAAC;AAC5E,QAAM,WAAW,MAAM,OAAO,CAAC,SAAS,KAAK,SAAS,WAAW,CAAC,EAAE;AAEpE,QAAM,UAAU,QACb;AAAA,IAAI,CAAC,WACJ;AAAA,MACE,oBAAoB,MAAM;AAAA,MAC1B;AAAA,MACA;AAAA,MACA,iBAAiB,aAAa,MAAM,CAAC;AAAA,IACvC,EAAE,KAAK,IAAI;AAAA,EACb,EACC,KAAK,IAAI;AAEZ,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,QAAM,eAAe;AAAA,IACnB,CAAC,UAAU,QAAQ,QAAQ,UAAU;AAAA,IACrC,QAAQ,IAAI,CAAC,WAAW;AAAA,MACtB;AAAA,MACA,KAAK,aAAa,MAAM,CAAC;AAAA,MACzB,OAAO,aAAa,MAAM,KAAK,CAAC;AAAA,MAChC,oBAAoB,MAAM;AAAA,IAC5B,CAAC;AAAA,EACH;AAEA,QAAM,cAAc,cAAc,CAAC,QAAQ,UAAU,OAAO,GAAG,eAAe,KAAK,CAAC;AAEpF,QAAM,aAAa;AAAA,IACjB,CAAC,QAAQ,UAAU;AAAA,IACnB,OAAO,QAAQ,WAAW,EAAE,IAAI,CAAC,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM,OAAO,KAAK,CAAC,CAAC;AAAA,EAC1E;AAEA,QAAM,eAAe,mBACjB,uHACA,8CAA8C,SAAS,SAAS;AAEpE,SAAO,GAAG,WAAW;AAAA;AAAA;AAAA;AAAA,gIAIyG,cAAc;AAAA;AAAA,+QAEiI,SAAS,QAAQ;AAAA;AAAA;AAAA;AAAA,EAI9R,YAAY;AAAA;AAAA,eAEC,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAOP,cAAc,IAAI,CAAC,SAAS,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,0PAG0K,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAOzO,YAAY,IAAI,CAAC,SAAS,KAAK,IAAI,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,aAC1D,OAAO,IAAI,CAAC,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,iBACtC,YAAY,KAAK,IAAI,CAAC;AAAA,YAC3B,OAAO,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,eACrC,UAAU,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,mBACvC,SAAS,KAAK,IAAI,CAAC;AAAA,2EACqC,cAAc,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,EAExH,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYZ,WAAW;AAAA;AAAA,eAEE,MAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzB,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBZ;;;AC/LO,IAAM,cAAoC;AAAA,EAC/C;AAAA;AAAA,IAEE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,MAAM;AAAA,EACjB;AAAA,EACA;AAAA;AAAA;AAAA,IAGE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,MAAM;AAAA,EACjB;AAAA,EACA;AAAA;AAAA,IAEE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,MAAM;AAAA,EACjB;AAAA,EACA;AAAA;AAAA;AAAA,IAGE,MAAM;AAAA,IACN,SACE;AAAA,IACF,SAAS,CAAC,QAAQ,WAAW,GAAG,MAAM;AAAA,EACxC;AAAA,EACA;AAAA;AAAA;AAAA,IAGE,MAAM;AAAA,IACN,SACE;AAAA,IACF,SAAS,CAAC,QAAQ,SAAS,GAAG,IAAI;AAAA,EACpC;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,CAAC,QAAQ,WAAW,GAAG,MAAM;AAAA,EACxC;AAAA,EACA;AAAA;AAAA;AAAA,IAGE,MAAM;AAAA,IACN,SACE;AAAA,IACF,SAAS,MAAM;AAAA,EACjB;AAAA,EACA;AAAA;AAAA;AAAA,IAGE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,CAAC,QAAQ,WAAW,GAAG,UAAU,EAAE;AAAA,EAC9C;AAAA,EACA;AAAA;AAAA;AAAA,IAGE,MAAM;AAAA,IACN,SAAS;AAAA,IACT,SAAS,MAAM;AAAA,EACjB;AACF;AAKO,SAAS,mBAAgC;AAC9C,SAAO,OAAO,YAAY,YAAY,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC;AACrE;AAEO,SAAS,eAAe,MAAmB,MAAgC;AAChF,aAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,IAAI,EAAG,MAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK;AACnF,SAAO;AACT;AAEO,SAAS,UAAU,MAAc,QAA6B;AACnE,MAAI,MAAM;AACV,aAAW,QAAQ,aAAa;AAC9B,UAAM,IAAI,QAAQ,KAAK,SAAS,CAAC,UAAkB,SAAoB;AACrE,aAAO,KAAK,IAAI,KAAK,OAAO,KAAK,IAAI,KAAK,KAAK;AAC/C,aAAO,KAAK,QAAQ,OAAO,OAAO,KAAK,CAAC,MAAM,WAAW,KAAK,CAAC,IAAI,MAAS;AAAA,IAC9E,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,WAAW,OAAgB,QAA8B;AAChE,MAAI,OAAO,UAAU,SAAU,QAAO,UAAU,OAAO,MAAM;AAC7D,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,IAAI,CAAC,SAAS,WAAW,MAAM,MAAM,CAAC;AAC7E,MAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;AAC/C,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,IAAI,KAAK,OAAO,QAAQ,KAAK,EAAG,KAAI,GAAG,IAAI,WAAW,MAAM,MAAM;AACnF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,iBAAiB,MAAmB,QAAkC;AACpF,SAAO,WAAW,MAAM,MAAM;AAChC;AAEO,SAAS,WAAW,OAAqE;AAC9F,QAAM,SAAS,iBAAiB;AAChC,SAAO,EAAE,OAAO,MAAM,IAAI,CAAC,SAAS,iBAAiB,MAAM,MAAM,CAAC,GAAG,OAAO;AAC9E;AASO,SAAS,uBAAuB,MAAsB;AAC3D,SAAO,UAAU,MAAM,iBAAiB,CAAC;AAC3C;;;ACzHA,SAAS,iBAAiB;AAC1B,SAAS,SAAAA,QAAO,aAAAC,kBAAiB;AACjC,SAAS,UAAU,WAAAC,UAAS,YAAY;AA6BxC,eAAsB,eACpB,QACA,SACuB;AACvB,MAAI,OAAO,WAAW,EAAG,OAAM,IAAI,MAAM,wBAAwB;AACjE,MAAI,QAAQ,QAAQ,WAAW,EAAG,OAAM,IAAI,MAAM,qBAAqB;AAEvE,QAAM,SAAsB;AAAA,IAC1B,OAAO,CAAC;AAAA,IACR,QAAQ,iBAAiB;AAAA,IACzB,UAAU,EAAE,WAAW,GAAG,UAAU,EAAE;AAAA,EACxC;AACA,QAAM,OAAsB,CAAC;AAC7B,MAAI,OAAO;AAEX,aAAW,SAAS,QAAQ;AAC1B,UAAM,QAAQ,MAAM,kBAAkB,KAAK;AAC3C,YAAQ,MAAM;AACd,UAAM,YAAY,MAAM,OAAO,CAAC,SAAS;AACvC,UAAI,CAAC,iBAAiB,KAAK,KAAK,KAAK,GAAG;AACtC,eAAO,SAAS,YAAY;AAC5B,eAAO;AAAA,MACT;AACA,UAAI,CAAC,QAAQ,oBAAoB,KAAK,SAAS,YAAY;AACzD,eAAO,SAAS,aAAa;AAC7B,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,CAAC;AACD,UAAM,WAAW,WAAW,SAAS;AACrC,WAAO,MAAM,KAAK,IAAI,SAAS;AAC/B,mBAAe,OAAO,QAAQ,SAAS,MAAM;AAC7C,SAAK,KAAK,GAAG,SAAS,KAAK;AAAA,EAC7B;AAEA,QAAM,eAAuD,CAAC;AAC9D,QAAM,QAAkB,CAAC;AAEzB,aAAW,UAAU,QAAQ,SAAS;AACpC,UAAM,OAAO,KAAK,QAAQ,KAAK,aAAa,MAAM,CAAC;AACnD,UAAMC,OAAMC,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,QAAI,WAAW,OAAO;AAEpB,YAAM,mBAAmB,MAAM,IAAI;AACnC,mBAAa,MAAM,KAAK;AAAA,IAC1B,WAAW,WAAW,OAAO;AAC3B,YAAM,OAAO,UAAU,IAAI;AAC3B,YAAMC,WAAU,MAAM,QAAQ,IAAI,CAAC;AACnC,mBAAa,MAAM,KAAK;AAAA,IAC1B,WAAW,WAAW,aAAa;AACjC,YAAM,UAAU,0BAA0B,IAAI;AAC9C,YAAMA,WAAU,MAAM,QAAQ,OAAO,CAAC;AACtC,mBAAa,YAAY,QAAQ;AAAA,IACnC,OAAO;AACL,YAAM,QAAQ,WAAW,IAAI;AAC7B,YAAMA,WAAU,MAAM,QAAQ,KAAK,CAAC;AACpC,mBAAa,MAAM,MAAM;AAAA,IAC3B;AACA,UAAM,KAAK,IAAI;AAAA,EACjB;AAEA,QAAM,aAAa,KAAK,QAAQ,KAAK,mBAAmB;AACxD,QAAMA,WAAU,YAAY,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,CAAI;AAClE,QAAM,KAAK,UAAU;AAErB,QAAM,WAAW,KAAK,QAAQ,KAAK,WAAW;AAC9C,QAAMA;AAAA,IACJ;AAAA,IACA,iBAAiB;AAAA,MACf,OAAO;AAAA,MACP,SAAS,QAAQ;AAAA,MACjB,kBAAkB,QAAQ;AAAA,MAC1B,aAAa,OAAO,IAAI,CAAC,UAAU,SAAS,KAAK,CAAC;AAAA,MAClD,aAAa,OAAO;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB;AAAA,IACF,CAAC;AAAA,EACH;AACA,QAAM,KAAK,QAAQ;AAEnB,SAAO,EAAE,QAAQ,MAAM,MAAM,KAAK,QAAQ,OAAO,QAAQ,cAAc,MAAM;AAC/E;AAEO,SAAS,gBAAgB,MAAc,QAA0B;AACtE,SAAO,CAAC,mBAAmB,UAAU,MAAM,QAAQ,KAAK,eAAe,SAAS;AAClF;AAEO,SAAS,YAAY,MAAc,QAAsB;AAC9D,QAAM,QAAQ,UAAU,SAAS,CAAC,iBAAiB,GAAG,EAAE,OAAO,SAAS,CAAC;AACzE,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,QAAQ,IAAI,UAAU;AACzB,UAAM,IAAI,MAAM,qDAAgD;AAAA,EAClE;AACA,QAAM,CAAC,SAAS,GAAG,IAAI,IAAI,gBAAgB,MAAM,MAAM;AAEvD,QAAM,MAAM,UAAU,SAAS,MAAM,EAAE,OAAO,UAAU,CAAC;AACzD,MAAI,IAAI,WAAW,EAAG,OAAM,IAAI,MAAM,iCAAiC,OAAO,IAAI,MAAM,CAAC,EAAE;AAC7F;AAOO,IAAM,wBACX;AAEK,SAAS,wBAAwB,MAAuC;AAC7E,QAAM,OAA8B;AAAA,IAClC,QAAQ,CAAC;AAAA,IACT,KAAK;AAAA,IACL,SAAS,CAAC,GAAG,eAAe;AAAA,IAC5B,kBAAkB;AAAA,IAClB,MAAM;AAAA,EACR;AACA,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,MAAM,KAAK,CAAC;AAClB,QAAI,QAAQ,SAAS;AACnB,WAAK,MAAM,KAAK,EAAE,CAAC,KAAK;AAAA,IAC1B,WAAW,QAAQ,aAAa;AAC9B,YAAM,OAAO,KAAK,EAAE,CAAC,KAAK,IAAI,MAAM,GAAG,EAAE,OAAO,OAAO;AACvD,iBAAW,UAAU,KAAK;AACxB,YAAI,CAAC,gBAAgB,SAAS,MAAuB,GAAG;AACtD,gBAAM,IAAI;AAAA,YACR,mBAAmB,MAAM,4BAAuB,gBAAgB,KAAK,GAAG,CAAC;AAAA,UAC3E;AAAA,QACF;AAAA,MACF;AACA,WAAK,UAAU;AAAA,IACjB,WAAW,QAAQ,uBAAuB;AACxC,WAAK,mBAAmB;AAAA,IAC1B,WAAW,QAAQ,UAAU;AAC3B,WAAK,OAAO,KAAK,EAAE,CAAC,KAAK;AAAA,IAC3B,WAAW,IAAI,WAAW,IAAI,GAAG;AAC/B,YAAM,IAAI,MAAM,iBAAiB,GAAG,GAAG;AAAA,IACzC,OAAO;AACL,WAAK,OAAO,KAAK,GAAG;AAAA,IACtB;AAAA,EACF;AACA,MAAI,KAAK,OAAO,WAAW,KAAK,CAAC,KAAK,KAAK;AACzC,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AACA,MAAI,KAAK,SAAS,QAAQ,CAAC,qBAAqB,KAAK,KAAK,IAAI,GAAG;AAC/D,UAAM,IAAI,MAAM,mCAAmC,KAAK,IAAI,GAAG;AAAA,EACjE;AACA,SAAO;AACT;AAGA,eAAsB,qBAAqB,MAAiC;AAC1E,MAAI;AACJ,MAAI;AACF,WAAO,wBAAwB,IAAI;AAAA,EACrC,SAAS,OAAO;AACd,YAAQ,OAAO,MAAM,GAAG,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;AAAA,CAAI;AAClF,WAAO;AAAA,EACT;AACA,QAAM,UAAU,MAAM,eAAe,KAAK,QAAQ,IAAI;AACtD,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK;AAAA,MACN;AAAA,QACE,MAAM,QAAQ;AAAA,QACd,MAAM,QAAQ;AAAA,QACd,cAAc,QAAQ;AAAA,QACtB,OAAO,QAAQ;AAAA,MACjB;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA;AAAA,EACH;AACA,UAAQ,OAAO,MAAM,kBAAa,KAAK,GAAG,KAAK,QAAQ,MAAM,MAAM;AAAA,CAAW;AAC9E,MAAI,KAAK,SAAS,MAAM;AACtB,gBAAY,KAAK,MAAM,KAAK,GAAG;AAC/B,YAAQ,OAAO,MAAM,iBAAY,KAAK,IAAI;AAAA,CAAI;AAAA,EAChD;AACA,SAAO;AACT;","names":["mkdir","writeFile","dirname","mkdir","dirname","writeFile"]}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// src/rollout/schema.ts
|
|
2
|
+
var ROLLOUT_SCHEMA = "tangle.rollout.v1";
|
|
3
|
+
var ROLLOUT_FORMAT = ROLLOUT_SCHEMA;
|
|
4
|
+
var ROLLOUT_ROLES = [
|
|
5
|
+
"agent",
|
|
6
|
+
"supervisor",
|
|
7
|
+
"worker",
|
|
8
|
+
"proposer",
|
|
9
|
+
"judge",
|
|
10
|
+
"analyst"
|
|
11
|
+
];
|
|
12
|
+
var ROLLOUT_SPLITS = [
|
|
13
|
+
"search",
|
|
14
|
+
"dev",
|
|
15
|
+
"holdout",
|
|
16
|
+
"canary",
|
|
17
|
+
"train"
|
|
18
|
+
];
|
|
19
|
+
var TRAINABLE_SPLITS = ["search", "train"];
|
|
20
|
+
function isTrainableSplit(split) {
|
|
21
|
+
return TRAINABLE_SPLITS.includes(split);
|
|
22
|
+
}
|
|
23
|
+
var ROLLOUT_CAPTURES = ["mint", "settle-time", "backfill"];
|
|
24
|
+
var CHAT_ROLES = ["system", "user", "assistant", "tool"];
|
|
25
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
26
|
+
var isNumberOrNull = (v) => v === null || typeof v === "number";
|
|
27
|
+
var isStringOrNull = (v) => v === null || typeof v === "string";
|
|
28
|
+
var isIntegerOrNull = (v) => v === null || Number.isInteger(v);
|
|
29
|
+
function validateChatMessage(value, path, errors) {
|
|
30
|
+
if (!isRecord(value)) {
|
|
31
|
+
errors.push(`${path}: not an object`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!CHAT_ROLES.includes(value.role))
|
|
35
|
+
errors.push(`${path}.role: invalid role ${String(value.role)}`);
|
|
36
|
+
if (!isStringOrNull(value.content)) errors.push(`${path}.content: must be string|null`);
|
|
37
|
+
if (value.reasoning_content !== void 0 && typeof value.reasoning_content !== "string") {
|
|
38
|
+
errors.push(`${path}.reasoning_content: must be string when present`);
|
|
39
|
+
}
|
|
40
|
+
if (value.tool_call_id !== void 0 && typeof value.tool_call_id !== "string") {
|
|
41
|
+
errors.push(`${path}.tool_call_id: must be string when present`);
|
|
42
|
+
}
|
|
43
|
+
if (value.role === "tool" && typeof value.tool_call_id !== "string") {
|
|
44
|
+
errors.push(`${path}.tool_call_id: required on role:"tool"`);
|
|
45
|
+
}
|
|
46
|
+
if (value.tool_calls !== void 0) {
|
|
47
|
+
if (!Array.isArray(value.tool_calls)) {
|
|
48
|
+
errors.push(`${path}.tool_calls: must be an array when present`);
|
|
49
|
+
} else {
|
|
50
|
+
value.tool_calls.forEach((call, i) => {
|
|
51
|
+
if (!isRecord(call) || typeof call.id !== "string" || call.type !== "function") {
|
|
52
|
+
errors.push(`${path}.tool_calls[${i}]: must be {id, type:"function", function}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const fn = call.function;
|
|
56
|
+
if (!isRecord(fn) || typeof fn.name !== "string" || typeof fn.arguments !== "string") {
|
|
57
|
+
errors.push(
|
|
58
|
+
`${path}.tool_calls[${i}].function: must be {name: string, arguments: string}`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function validateSection(value, path, fields, errors) {
|
|
66
|
+
if (!isRecord(value)) {
|
|
67
|
+
errors.push(`${path}: not an object`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const [name, check, expect] of fields) {
|
|
71
|
+
if (!check(value[name])) errors.push(`${path}.${name}: expected ${expect}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function validateRolloutLine(value) {
|
|
75
|
+
const errors = [];
|
|
76
|
+
if (!isRecord(value)) return ["line: not an object"];
|
|
77
|
+
if (value.schema !== ROLLOUT_SCHEMA) errors.push(`schema: expected "${ROLLOUT_SCHEMA}"`);
|
|
78
|
+
if (typeof value.rollout_id !== "string" || value.rollout_id.length === 0)
|
|
79
|
+
errors.push("rollout_id: expected non-empty string");
|
|
80
|
+
if (!isStringOrNull(value.parent_rollout_id))
|
|
81
|
+
errors.push("parent_rollout_id: expected string|null");
|
|
82
|
+
if (typeof value.run_id !== "string" || value.run_id.length === 0)
|
|
83
|
+
errors.push("run_id: expected non-empty string");
|
|
84
|
+
if (value.experiment_id !== void 0 && !isStringOrNull(value.experiment_id)) {
|
|
85
|
+
errors.push("experiment_id: expected string|null when present");
|
|
86
|
+
}
|
|
87
|
+
if (value.candidate_id !== void 0 && !isStringOrNull(value.candidate_id)) {
|
|
88
|
+
errors.push("candidate_id: expected string|null when present");
|
|
89
|
+
}
|
|
90
|
+
if (!isIntegerOrNull(value.generation)) errors.push("generation: expected integer|null");
|
|
91
|
+
if (!isIntegerOrNull(value.candidate_index)) errors.push("candidate_index: expected integer|null");
|
|
92
|
+
if (!ROLLOUT_ROLES.includes(value.role))
|
|
93
|
+
errors.push(`role: invalid role ${String(value.role)}`);
|
|
94
|
+
validateSection(
|
|
95
|
+
value.task,
|
|
96
|
+
"task",
|
|
97
|
+
[
|
|
98
|
+
["suite", (v) => typeof v === "string" && v.length > 0, "non-empty string"],
|
|
99
|
+
["instance_id", (v) => typeof v === "string" && v.length > 0, "non-empty string"],
|
|
100
|
+
[
|
|
101
|
+
"split",
|
|
102
|
+
(v) => ROLLOUT_SPLITS.includes(v),
|
|
103
|
+
`one of ${ROLLOUT_SPLITS.join("|")}`
|
|
104
|
+
],
|
|
105
|
+
["seed", isNumberOrNull, "number|null"],
|
|
106
|
+
["rep", (v) => Number.isInteger(v), "integer"]
|
|
107
|
+
],
|
|
108
|
+
errors
|
|
109
|
+
);
|
|
110
|
+
validateSection(
|
|
111
|
+
value.policy,
|
|
112
|
+
"policy",
|
|
113
|
+
[
|
|
114
|
+
["harness", isStringOrNull, "string|null"],
|
|
115
|
+
["harness_version", isStringOrNull, "string|null"],
|
|
116
|
+
["model", isStringOrNull, "string|null"],
|
|
117
|
+
["provider", isStringOrNull, "string|null"],
|
|
118
|
+
["profile_commit", isStringOrNull, "string|null"],
|
|
119
|
+
["sampling", (v) => v === null || isRecord(v), "object|null"]
|
|
120
|
+
],
|
|
121
|
+
errors
|
|
122
|
+
);
|
|
123
|
+
if (isRecord(value.policy)) {
|
|
124
|
+
for (const key of ["prompt_hash", "config_hash", "agent_profile_cell_id"]) {
|
|
125
|
+
if (value.policy[key] !== void 0 && !isStringOrNull(value.policy[key])) {
|
|
126
|
+
errors.push(`policy.${key}: expected string|null when present`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (!Array.isArray(value.messages)) {
|
|
131
|
+
errors.push("messages: expected array");
|
|
132
|
+
} else {
|
|
133
|
+
for (const [i, m] of value.messages.entries()) validateChatMessage(m, `messages[${i}]`, errors);
|
|
134
|
+
}
|
|
135
|
+
if (!Array.isArray(value.tool_defs)) {
|
|
136
|
+
errors.push("tool_defs: expected array");
|
|
137
|
+
} else {
|
|
138
|
+
value.tool_defs.forEach((d, i) => {
|
|
139
|
+
if (!isRecord(d) || d.type !== "function" || !isRecord(d.function) || typeof d.function.name !== "string") {
|
|
140
|
+
errors.push(`tool_defs[${i}]: must be {type:"function", function:{name}}`);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
if (value.steps !== void 0) {
|
|
145
|
+
if (!Array.isArray(value.steps)) {
|
|
146
|
+
errors.push("steps: expected array when present");
|
|
147
|
+
} else {
|
|
148
|
+
value.steps.forEach((s, i) => {
|
|
149
|
+
if (!isRecord(s) || typeof s.kind !== "string" || typeof s.name !== "string") {
|
|
150
|
+
errors.push(`steps[${i}]: must be {kind: string, name: string, \u2026}`);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
validateSection(
|
|
156
|
+
value.outcome,
|
|
157
|
+
"outcome",
|
|
158
|
+
[
|
|
159
|
+
["reward", isNumberOrNull, "number|null"],
|
|
160
|
+
["reward_source", isStringOrNull, "string|null"],
|
|
161
|
+
["metrics", isRecord, "object"],
|
|
162
|
+
["is_completed", (v) => typeof v === "boolean", "boolean"],
|
|
163
|
+
["is_truncated", (v) => typeof v === "boolean", "boolean"],
|
|
164
|
+
["error", isStringOrNull, "string|null"]
|
|
165
|
+
],
|
|
166
|
+
errors
|
|
167
|
+
);
|
|
168
|
+
if (isRecord(value.outcome)) {
|
|
169
|
+
if (!("verdict" in value.outcome)) errors.push("outcome.verdict: field required (may be null)");
|
|
170
|
+
if (value.outcome.realness_gated !== void 0 && typeof value.outcome.realness_gated !== "boolean") {
|
|
171
|
+
errors.push("outcome.realness_gated: expected boolean when present");
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
validateSection(
|
|
175
|
+
value.cost,
|
|
176
|
+
"cost",
|
|
177
|
+
[
|
|
178
|
+
["usd", isNumberOrNull, "number|null"],
|
|
179
|
+
["tokens_in", isNumberOrNull, "number|null"],
|
|
180
|
+
["tokens_out", isNumberOrNull, "number|null"],
|
|
181
|
+
["tokens_reasoning", isNumberOrNull, "number|null"],
|
|
182
|
+
["cache_read", isNumberOrNull, "number|null"],
|
|
183
|
+
["cache_write", isNumberOrNull, "number|null"],
|
|
184
|
+
["wall_s", isNumberOrNull, "number|null"]
|
|
185
|
+
],
|
|
186
|
+
errors
|
|
187
|
+
);
|
|
188
|
+
validateSection(
|
|
189
|
+
value.artifacts,
|
|
190
|
+
"artifacts",
|
|
191
|
+
[
|
|
192
|
+
["patch_path", isStringOrNull, "string|null"],
|
|
193
|
+
["run_dir", isStringOrNull, "string|null"],
|
|
194
|
+
["transcript_ref", isStringOrNull, "string|null"]
|
|
195
|
+
],
|
|
196
|
+
errors
|
|
197
|
+
);
|
|
198
|
+
validateSection(
|
|
199
|
+
value.provenance,
|
|
200
|
+
"provenance",
|
|
201
|
+
[
|
|
202
|
+
[
|
|
203
|
+
"captured_at",
|
|
204
|
+
(v) => typeof v === "string" && !Number.isNaN(Date.parse(v)),
|
|
205
|
+
"ISO-8601 timestamp"
|
|
206
|
+
],
|
|
207
|
+
[
|
|
208
|
+
"capture",
|
|
209
|
+
(v) => ROLLOUT_CAPTURES.includes(v),
|
|
210
|
+
`one of ${ROLLOUT_CAPTURES.join("|")}`
|
|
211
|
+
]
|
|
212
|
+
],
|
|
213
|
+
errors
|
|
214
|
+
);
|
|
215
|
+
if (isRecord(value.provenance) && value.provenance.gap !== void 0 && typeof value.provenance.gap !== "string") {
|
|
216
|
+
errors.push("provenance.gap: must be string when present");
|
|
217
|
+
}
|
|
218
|
+
if (Array.isArray(value.messages) && isRecord(value.provenance)) {
|
|
219
|
+
if (value.messages.length === 0 && typeof value.provenance.gap !== "string") {
|
|
220
|
+
errors.push("provenance.gap: required when messages is empty");
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return errors;
|
|
224
|
+
}
|
|
225
|
+
function assertRolloutLine(value, context = "rollout line") {
|
|
226
|
+
const errors = validateRolloutLine(value);
|
|
227
|
+
if (errors.length > 0) {
|
|
228
|
+
throw new Error(`invalid ${context}:
|
|
229
|
+
${errors.join("\n ")}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function isRolloutLine(value) {
|
|
233
|
+
return validateRolloutLine(value).length === 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export {
|
|
237
|
+
ROLLOUT_SCHEMA,
|
|
238
|
+
ROLLOUT_FORMAT,
|
|
239
|
+
ROLLOUT_ROLES,
|
|
240
|
+
ROLLOUT_SPLITS,
|
|
241
|
+
TRAINABLE_SPLITS,
|
|
242
|
+
isTrainableSplit,
|
|
243
|
+
ROLLOUT_CAPTURES,
|
|
244
|
+
CHAT_ROLES,
|
|
245
|
+
validateRolloutLine,
|
|
246
|
+
assertRolloutLine,
|
|
247
|
+
isRolloutLine
|
|
248
|
+
};
|
|
249
|
+
//# sourceMappingURL=chunk-MAX3TN3C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/rollout/schema.ts"],"sourcesContent":["/**\n * `tangle.rollout.v1` — THE canonical rollout serialization, owned by\n * agent-eval. One JSONL line per agent invocation (a solo eval run, a\n * supervisor episode, a worker session, a proposer shot, a judge call, an\n * analyst pass), labeled with its task/split coordinates and a single\n * scalar reward, carrying the FULL message transcript inline.\n *\n * This schema is the reconciliation of two prior producers:\n * - agent-eval's RunRecord-joined rollout rows (PR #410): identity,\n * provenance hashes, the realness gate travelling into the reward,\n * trace-derived steps.\n * - the bench rollout-ledger (agent-runtime PR #591): the wire shape —\n * role, task.split/rep, parent_rollout_id, policy provenance, capture\n * provenance, inline canonical chat-with-tools messages.\n * Where the two conflicted, RunRecord-derived semantics won; the wire\n * field names follow the ledger (snake_case). See `docs/rollout.md` for\n * the field-by-field decision table.\n *\n * Messages are inlined — never referenced — because every harness store a\n * rollout can be recovered from is mutable or garbage-collected. A line\n * must stay a complete training/eval example on its own.\n *\n * `outcome.reward` is THE single scalar (null = no verdict exists — a\n * labeled gap, never 0). `outcome.realness_gated` is the anti-Goodhart\n * flag: a gated line must never export as a positive training example.\n */\n\nexport const ROLLOUT_SCHEMA = 'tangle.rollout.v1'\n/** @deprecated alias kept for consumers of the pre-unification constant name. */\nexport const ROLLOUT_FORMAT = ROLLOUT_SCHEMA\n\n/** `agent` = a solo evaluation run (no multi-agent topology). */\nexport type RolloutRole = 'agent' | 'supervisor' | 'worker' | 'proposer' | 'judge' | 'analyst'\nexport const ROLLOUT_ROLES: readonly RolloutRole[] = [\n 'agent',\n 'supervisor',\n 'worker',\n 'proposer',\n 'judge',\n 'analyst',\n]\n\n/**\n * Split vocabulary follows `RunRecord.splitTag` ('search' is the pool the\n * optimizer may read — the trainable split), extended with the ledger's\n * 'canary'. 'train' is a legacy alias for 'search' emitted by\n * pre-unification ledgers; it validates and counts as trainable, but new\n * producers must emit 'search'.\n */\nexport type RolloutSplit = 'search' | 'dev' | 'holdout' | 'canary' | 'train'\nexport const ROLLOUT_SPLITS: readonly RolloutSplit[] = [\n 'search',\n 'dev',\n 'holdout',\n 'canary',\n 'train',\n]\n/** Splits that may ship in training exports. Everything else is fail-closed excluded. */\nexport const TRAINABLE_SPLITS: readonly RolloutSplit[] = ['search', 'train']\n\nexport function isTrainableSplit(split: RolloutSplit): boolean {\n return TRAINABLE_SPLITS.includes(split)\n}\n\n/** 'mint' = joined live from RunRecord + trace by `mintRolloutRows`. */\nexport type RolloutCapture = 'mint' | 'settle-time' | 'backfill'\nexport const ROLLOUT_CAPTURES: readonly RolloutCapture[] = ['mint', 'settle-time', 'backfill']\n\n// ---------------------------------------------------------------------------\n// Canonical message format — OpenAI chat-with-tools, full fidelity.\n// ---------------------------------------------------------------------------\n\nexport type ChatRole = 'system' | 'user' | 'assistant' | 'tool'\nexport const CHAT_ROLES: readonly ChatRole[] = ['system', 'user', 'assistant', 'tool']\n\nexport interface ChatToolCall {\n id: string\n type: 'function'\n function: {\n name: string\n /** JSON-encoded argument object, exactly as the model emitted it. */\n arguments: string\n }\n}\n\nexport interface ChatMessage {\n role: ChatRole\n content: string | null\n /** Reasoning/thinking channel where the harness captured it (full fidelity). */\n reasoning_content?: string\n tool_calls?: ChatToolCall[]\n /** Required on role:\"tool\" — the ChatToolCall this result answers. */\n tool_call_id?: string\n name?: string\n}\n\nexport interface ToolDef {\n type: 'function'\n function: {\n name: string\n description?: string\n parameters?: Record<string, unknown>\n }\n}\n\n/**\n * Compact trace-span projection (llm/tool step) carried alongside the\n * conversation when the line was minted from a trace. Optional: lines\n * recovered from harness stores have no span structure.\n */\nexport interface RolloutStep {\n kind: string\n name: string\n /** llm: last-message summary · tool: stringified args. Scrubbed. */\n input?: string\n /** llm: output text · tool: stringified result. Scrubbed. */\n output?: string\n status?: 'ok' | 'error'\n durationMs?: number\n}\n\n// ---------------------------------------------------------------------------\n// Ledger line sections.\n// ---------------------------------------------------------------------------\n\nexport interface RolloutTask {\n /** Benchmark/suite id (e.g. \"swe-bench-verified\") or the experiment id. */\n suite: string\n instance_id: string\n split: RolloutSplit\n /** Sampling seed the campaign pinned; null = not recorded. */\n seed: number | null\n /** Replicate index (0-based). */\n rep: number\n}\n\nexport interface RolloutPolicy {\n /** Harness that drove the invocation (e.g. \"opencode\", \"claude\", \"pi-loops\"). */\n harness: string | null\n harness_version: string | null\n model: string | null\n provider: string | null\n /** Commit of the agent profile / candidate under evaluation. */\n profile_commit: string | null\n /** sha256 of the effective prompt (post-steering), when recorded. */\n prompt_hash?: string | null\n /** sha256 of the effective run config, when recorded. */\n config_hash?: string | null\n /** Canonical agent-profile cell identity, when the run carries one. */\n agent_profile_cell_id?: string | null\n /** Sampling params (temperature, top_p, max_tokens…); null = not recorded. */\n sampling: Record<string, unknown> | null\n}\n\nexport interface RolloutOutcome {\n /**\n * THE single scalar training signal — the official verdict.\n * null = no verdict exists for this invocation (a labeled gap, never 0).\n */\n reward: number | null\n /** Where the reward came from (judge id; \"/inherited\" = parent episode's). */\n reward_source: string | null\n /** Raw judge verdict record, verbatim. */\n verdict: unknown\n /** Everything that is NOT the scalar reward. */\n metrics: Record<string, unknown>\n is_completed: boolean\n is_truncated: boolean\n error: string | null\n /**\n * Anti-Goodhart flag from `RunRecord.outcome.realness.gated`: the run\n * faked its success signal. Reward is forced to 0 at mint time and the\n * line never qualifies for SFT. Optional on the wire (absent = false)\n * so pre-unification ledgers stay readable.\n */\n realness_gated?: boolean\n}\n\nexport interface RolloutCostBlock {\n usd: number | null\n tokens_in: number | null\n tokens_out: number | null\n tokens_reasoning: number | null\n cache_read: number | null\n cache_write: number | null\n wall_s: number | null\n}\n\nexport interface RolloutArtifacts {\n patch_path: string | null\n run_dir: string | null\n /** Source-of-truth transcript pointer (session id / jsonl path) for audit. */\n transcript_ref: string | null\n}\n\nexport interface RolloutProvenance {\n captured_at: string\n capture: RolloutCapture\n /** Present on gap lines: why `messages` could not be recovered. */\n gap?: string\n}\n\nexport interface RolloutLine {\n schema: typeof ROLLOUT_SCHEMA\n rollout_id: string\n /** Spawning invocation within the same episode (worker → supervisor). */\n parent_rollout_id: string | null\n run_id: string\n /** Logical experiment grouping from `RunRecord.experimentId`. Optional on\n * the wire (pre-unification ledgers lack it); null = not recorded. */\n experiment_id?: string | null\n /** Stable candidate identity from `RunRecord.candidateId`; null = not recorded. */\n candidate_id?: string | null\n /** Improvement-loop generation (-1 = baseline); null = not an improvement loop. */\n generation: number | null\n /** Improvement-loop candidate index (-1 = baseline); null = not an improvement loop. */\n candidate_index: number | null\n role: RolloutRole\n task: RolloutTask\n policy: RolloutPolicy\n /** Full transcript, inline. [] = gap line (see provenance.gap). */\n messages: ChatMessage[]\n tool_defs: ToolDef[]\n /** Trace-span projections, when minted from a trace. */\n steps?: RolloutStep[]\n outcome: RolloutOutcome\n cost: RolloutCostBlock\n artifacts: RolloutArtifacts\n provenance: RolloutProvenance\n}\n\n// ---------------------------------------------------------------------------\n// Validation — pure TS, no runtime schema dependency, mirroring the\n// run-record validator's fail-loud discipline. Returns [] when the value\n// is a valid RolloutLine; otherwise one dotted-path error per defect.\n// ---------------------------------------------------------------------------\n\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === 'object' && v !== null && !Array.isArray(v)\n\nconst isNumberOrNull = (v: unknown): boolean => v === null || typeof v === 'number'\nconst isStringOrNull = (v: unknown): boolean => v === null || typeof v === 'string'\nconst isIntegerOrNull = (v: unknown): boolean => v === null || Number.isInteger(v)\n\nfunction validateChatMessage(value: unknown, path: string, errors: string[]): void {\n if (!isRecord(value)) {\n errors.push(`${path}: not an object`)\n return\n }\n if (!CHAT_ROLES.includes(value.role as ChatRole))\n errors.push(`${path}.role: invalid role ${String(value.role)}`)\n if (!isStringOrNull(value.content)) errors.push(`${path}.content: must be string|null`)\n if (value.reasoning_content !== undefined && typeof value.reasoning_content !== 'string') {\n errors.push(`${path}.reasoning_content: must be string when present`)\n }\n if (value.tool_call_id !== undefined && typeof value.tool_call_id !== 'string') {\n errors.push(`${path}.tool_call_id: must be string when present`)\n }\n if (value.role === 'tool' && typeof value.tool_call_id !== 'string') {\n errors.push(`${path}.tool_call_id: required on role:\"tool\"`)\n }\n if (value.tool_calls !== undefined) {\n if (!Array.isArray(value.tool_calls)) {\n errors.push(`${path}.tool_calls: must be an array when present`)\n } else {\n value.tool_calls.forEach((call, i) => {\n if (!isRecord(call) || typeof call.id !== 'string' || call.type !== 'function') {\n errors.push(`${path}.tool_calls[${i}]: must be {id, type:\"function\", function}`)\n return\n }\n const fn = call.function\n if (!isRecord(fn) || typeof fn.name !== 'string' || typeof fn.arguments !== 'string') {\n errors.push(\n `${path}.tool_calls[${i}].function: must be {name: string, arguments: string}`,\n )\n }\n })\n }\n }\n}\n\nfunction validateSection(\n value: unknown,\n path: string,\n fields: Array<[name: string, check: (v: unknown) => boolean, expect: string]>,\n errors: string[],\n): void {\n if (!isRecord(value)) {\n errors.push(`${path}: not an object`)\n return\n }\n for (const [name, check, expect] of fields) {\n if (!check(value[name])) errors.push(`${path}.${name}: expected ${expect}`)\n }\n}\n\nexport function validateRolloutLine(value: unknown): string[] {\n const errors: string[] = []\n if (!isRecord(value)) return ['line: not an object']\n\n if (value.schema !== ROLLOUT_SCHEMA) errors.push(`schema: expected \"${ROLLOUT_SCHEMA}\"`)\n if (typeof value.rollout_id !== 'string' || value.rollout_id.length === 0)\n errors.push('rollout_id: expected non-empty string')\n if (!isStringOrNull(value.parent_rollout_id))\n errors.push('parent_rollout_id: expected string|null')\n if (typeof value.run_id !== 'string' || value.run_id.length === 0)\n errors.push('run_id: expected non-empty string')\n if (value.experiment_id !== undefined && !isStringOrNull(value.experiment_id)) {\n errors.push('experiment_id: expected string|null when present')\n }\n if (value.candidate_id !== undefined && !isStringOrNull(value.candidate_id)) {\n errors.push('candidate_id: expected string|null when present')\n }\n if (!isIntegerOrNull(value.generation)) errors.push('generation: expected integer|null')\n if (!isIntegerOrNull(value.candidate_index)) errors.push('candidate_index: expected integer|null')\n if (!ROLLOUT_ROLES.includes(value.role as RolloutRole))\n errors.push(`role: invalid role ${String(value.role)}`)\n\n validateSection(\n value.task,\n 'task',\n [\n ['suite', (v) => typeof v === 'string' && v.length > 0, 'non-empty string'],\n ['instance_id', (v) => typeof v === 'string' && v.length > 0, 'non-empty string'],\n [\n 'split',\n (v) => ROLLOUT_SPLITS.includes(v as RolloutSplit),\n `one of ${ROLLOUT_SPLITS.join('|')}`,\n ],\n ['seed', isNumberOrNull, 'number|null'],\n ['rep', (v) => Number.isInteger(v), 'integer'],\n ],\n errors,\n )\n\n validateSection(\n value.policy,\n 'policy',\n [\n ['harness', isStringOrNull, 'string|null'],\n ['harness_version', isStringOrNull, 'string|null'],\n ['model', isStringOrNull, 'string|null'],\n ['provider', isStringOrNull, 'string|null'],\n ['profile_commit', isStringOrNull, 'string|null'],\n ['sampling', (v) => v === null || isRecord(v), 'object|null'],\n ],\n errors,\n )\n if (isRecord(value.policy)) {\n for (const key of ['prompt_hash', 'config_hash', 'agent_profile_cell_id'] as const) {\n if (value.policy[key] !== undefined && !isStringOrNull(value.policy[key])) {\n errors.push(`policy.${key}: expected string|null when present`)\n }\n }\n }\n\n if (!Array.isArray(value.messages)) {\n errors.push('messages: expected array')\n } else {\n for (const [i, m] of value.messages.entries()) validateChatMessage(m, `messages[${i}]`, errors)\n }\n\n if (!Array.isArray(value.tool_defs)) {\n errors.push('tool_defs: expected array')\n } else {\n value.tool_defs.forEach((d, i) => {\n if (\n !isRecord(d) ||\n d.type !== 'function' ||\n !isRecord(d.function) ||\n typeof d.function.name !== 'string'\n ) {\n errors.push(`tool_defs[${i}]: must be {type:\"function\", function:{name}}`)\n }\n })\n }\n\n if (value.steps !== undefined) {\n if (!Array.isArray(value.steps)) {\n errors.push('steps: expected array when present')\n } else {\n value.steps.forEach((s, i) => {\n if (!isRecord(s) || typeof s.kind !== 'string' || typeof s.name !== 'string') {\n errors.push(`steps[${i}]: must be {kind: string, name: string, …}`)\n }\n })\n }\n }\n\n validateSection(\n value.outcome,\n 'outcome',\n [\n ['reward', isNumberOrNull, 'number|null'],\n ['reward_source', isStringOrNull, 'string|null'],\n ['metrics', isRecord, 'object'],\n ['is_completed', (v) => typeof v === 'boolean', 'boolean'],\n ['is_truncated', (v) => typeof v === 'boolean', 'boolean'],\n ['error', isStringOrNull, 'string|null'],\n ],\n errors,\n )\n if (isRecord(value.outcome)) {\n if (!('verdict' in value.outcome)) errors.push('outcome.verdict: field required (may be null)')\n if (\n value.outcome.realness_gated !== undefined &&\n typeof value.outcome.realness_gated !== 'boolean'\n ) {\n errors.push('outcome.realness_gated: expected boolean when present')\n }\n }\n\n validateSection(\n value.cost,\n 'cost',\n [\n ['usd', isNumberOrNull, 'number|null'],\n ['tokens_in', isNumberOrNull, 'number|null'],\n ['tokens_out', isNumberOrNull, 'number|null'],\n ['tokens_reasoning', isNumberOrNull, 'number|null'],\n ['cache_read', isNumberOrNull, 'number|null'],\n ['cache_write', isNumberOrNull, 'number|null'],\n ['wall_s', isNumberOrNull, 'number|null'],\n ],\n errors,\n )\n\n validateSection(\n value.artifacts,\n 'artifacts',\n [\n ['patch_path', isStringOrNull, 'string|null'],\n ['run_dir', isStringOrNull, 'string|null'],\n ['transcript_ref', isStringOrNull, 'string|null'],\n ],\n errors,\n )\n\n validateSection(\n value.provenance,\n 'provenance',\n [\n [\n 'captured_at',\n (v) => typeof v === 'string' && !Number.isNaN(Date.parse(v)),\n 'ISO-8601 timestamp',\n ],\n [\n 'capture',\n (v) => ROLLOUT_CAPTURES.includes(v as RolloutCapture),\n `one of ${ROLLOUT_CAPTURES.join('|')}`,\n ],\n ],\n errors,\n )\n if (\n isRecord(value.provenance) &&\n value.provenance.gap !== undefined &&\n typeof value.provenance.gap !== 'string'\n ) {\n errors.push('provenance.gap: must be string when present')\n }\n\n // A gap line must say WHY it is a gap; a full line must not carry a gap note.\n if (Array.isArray(value.messages) && isRecord(value.provenance)) {\n if (value.messages.length === 0 && typeof value.provenance.gap !== 'string') {\n errors.push('provenance.gap: required when messages is empty')\n }\n }\n\n return errors\n}\n\nexport function assertRolloutLine(\n value: unknown,\n context = 'rollout line',\n): asserts value is RolloutLine {\n const errors = validateRolloutLine(value)\n if (errors.length > 0) {\n throw new Error(`invalid ${context}:\\n ${errors.join('\\n ')}`)\n }\n}\n\nexport function isRolloutLine(value: unknown): value is RolloutLine {\n return validateRolloutLine(value).length === 0\n}\n"],"mappings":";AA2BO,IAAM,iBAAiB;AAEvB,IAAM,iBAAiB;AAIvB,IAAM,gBAAwC;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUO,IAAM,iBAA0C;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,mBAA4C,CAAC,UAAU,OAAO;AAEpE,SAAS,iBAAiB,OAA8B;AAC7D,SAAO,iBAAiB,SAAS,KAAK;AACxC;AAIO,IAAM,mBAA8C,CAAC,QAAQ,eAAe,UAAU;AAOtF,IAAM,aAAkC,CAAC,UAAU,QAAQ,aAAa,MAAM;AAoKrF,IAAM,WAAW,CAAC,MAChB,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAEzD,IAAM,iBAAiB,CAAC,MAAwB,MAAM,QAAQ,OAAO,MAAM;AAC3E,IAAM,iBAAiB,CAAC,MAAwB,MAAM,QAAQ,OAAO,MAAM;AAC3E,IAAM,kBAAkB,CAAC,MAAwB,MAAM,QAAQ,OAAO,UAAU,CAAC;AAEjF,SAAS,oBAAoB,OAAgB,MAAc,QAAwB;AACjF,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO,KAAK,GAAG,IAAI,iBAAiB;AACpC;AAAA,EACF;AACA,MAAI,CAAC,WAAW,SAAS,MAAM,IAAgB;AAC7C,WAAO,KAAK,GAAG,IAAI,uBAAuB,OAAO,MAAM,IAAI,CAAC,EAAE;AAChE,MAAI,CAAC,eAAe,MAAM,OAAO,EAAG,QAAO,KAAK,GAAG,IAAI,+BAA+B;AACtF,MAAI,MAAM,sBAAsB,UAAa,OAAO,MAAM,sBAAsB,UAAU;AACxF,WAAO,KAAK,GAAG,IAAI,iDAAiD;AAAA,EACtE;AACA,MAAI,MAAM,iBAAiB,UAAa,OAAO,MAAM,iBAAiB,UAAU;AAC9E,WAAO,KAAK,GAAG,IAAI,4CAA4C;AAAA,EACjE;AACA,MAAI,MAAM,SAAS,UAAU,OAAO,MAAM,iBAAiB,UAAU;AACnE,WAAO,KAAK,GAAG,IAAI,wCAAwC;AAAA,EAC7D;AACA,MAAI,MAAM,eAAe,QAAW;AAClC,QAAI,CAAC,MAAM,QAAQ,MAAM,UAAU,GAAG;AACpC,aAAO,KAAK,GAAG,IAAI,4CAA4C;AAAA,IACjE,OAAO;AACL,YAAM,WAAW,QAAQ,CAAC,MAAM,MAAM;AACpC,YAAI,CAAC,SAAS,IAAI,KAAK,OAAO,KAAK,OAAO,YAAY,KAAK,SAAS,YAAY;AAC9E,iBAAO,KAAK,GAAG,IAAI,eAAe,CAAC,4CAA4C;AAC/E;AAAA,QACF;AACA,cAAM,KAAK,KAAK;AAChB,YAAI,CAAC,SAAS,EAAE,KAAK,OAAO,GAAG,SAAS,YAAY,OAAO,GAAG,cAAc,UAAU;AACpF,iBAAO;AAAA,YACL,GAAG,IAAI,eAAe,CAAC;AAAA,UACzB;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEA,SAAS,gBACP,OACA,MACA,QACA,QACM;AACN,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO,KAAK,GAAG,IAAI,iBAAiB;AACpC;AAAA,EACF;AACA,aAAW,CAAC,MAAM,OAAO,MAAM,KAAK,QAAQ;AAC1C,QAAI,CAAC,MAAM,MAAM,IAAI,CAAC,EAAG,QAAO,KAAK,GAAG,IAAI,IAAI,IAAI,cAAc,MAAM,EAAE;AAAA,EAC5E;AACF;AAEO,SAAS,oBAAoB,OAA0B;AAC5D,QAAM,SAAmB,CAAC;AAC1B,MAAI,CAAC,SAAS,KAAK,EAAG,QAAO,CAAC,qBAAqB;AAEnD,MAAI,MAAM,WAAW,eAAgB,QAAO,KAAK,qBAAqB,cAAc,GAAG;AACvF,MAAI,OAAO,MAAM,eAAe,YAAY,MAAM,WAAW,WAAW;AACtE,WAAO,KAAK,uCAAuC;AACrD,MAAI,CAAC,eAAe,MAAM,iBAAiB;AACzC,WAAO,KAAK,yCAAyC;AACvD,MAAI,OAAO,MAAM,WAAW,YAAY,MAAM,OAAO,WAAW;AAC9D,WAAO,KAAK,mCAAmC;AACjD,MAAI,MAAM,kBAAkB,UAAa,CAAC,eAAe,MAAM,aAAa,GAAG;AAC7E,WAAO,KAAK,kDAAkD;AAAA,EAChE;AACA,MAAI,MAAM,iBAAiB,UAAa,CAAC,eAAe,MAAM,YAAY,GAAG;AAC3E,WAAO,KAAK,iDAAiD;AAAA,EAC/D;AACA,MAAI,CAAC,gBAAgB,MAAM,UAAU,EAAG,QAAO,KAAK,mCAAmC;AACvF,MAAI,CAAC,gBAAgB,MAAM,eAAe,EAAG,QAAO,KAAK,wCAAwC;AACjG,MAAI,CAAC,cAAc,SAAS,MAAM,IAAmB;AACnD,WAAO,KAAK,sBAAsB,OAAO,MAAM,IAAI,CAAC,EAAE;AAExD;AAAA,IACE,MAAM;AAAA,IACN;AAAA,IACA;AAAA,MACE,CAAC,SAAS,CAAC,MAAM,OAAO,MAAM,YAAY,EAAE,SAAS,GAAG,kBAAkB;AAAA,MAC1E,CAAC,eAAe,CAAC,MAAM,OAAO,MAAM,YAAY,EAAE,SAAS,GAAG,kBAAkB;AAAA,MAChF;AAAA,QACE;AAAA,QACA,CAAC,MAAM,eAAe,SAAS,CAAiB;AAAA,QAChD,UAAU,eAAe,KAAK,GAAG,CAAC;AAAA,MACpC;AAAA,MACA,CAAC,QAAQ,gBAAgB,aAAa;AAAA,MACtC,CAAC,OAAO,CAAC,MAAM,OAAO,UAAU,CAAC,GAAG,SAAS;AAAA,IAC/C;AAAA,IACA;AAAA,EACF;AAEA;AAAA,IACE,MAAM;AAAA,IACN;AAAA,IACA;AAAA,MACE,CAAC,WAAW,gBAAgB,aAAa;AAAA,MACzC,CAAC,mBAAmB,gBAAgB,aAAa;AAAA,MACjD,CAAC,SAAS,gBAAgB,aAAa;AAAA,MACvC,CAAC,YAAY,gBAAgB,aAAa;AAAA,MAC1C,CAAC,kBAAkB,gBAAgB,aAAa;AAAA,MAChD,CAAC,YAAY,CAAC,MAAM,MAAM,QAAQ,SAAS,CAAC,GAAG,aAAa;AAAA,IAC9D;AAAA,IACA;AAAA,EACF;AACA,MAAI,SAAS,MAAM,MAAM,GAAG;AAC1B,eAAW,OAAO,CAAC,eAAe,eAAe,uBAAuB,GAAY;AAClF,UAAI,MAAM,OAAO,GAAG,MAAM,UAAa,CAAC,eAAe,MAAM,OAAO,GAAG,CAAC,GAAG;AACzE,eAAO,KAAK,UAAU,GAAG,qCAAqC;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAEA,MAAI,CAAC,MAAM,QAAQ,MAAM,QAAQ,GAAG;AAClC,WAAO,KAAK,0BAA0B;AAAA,EACxC,OAAO;AACL,eAAW,CAAC,GAAG,CAAC,KAAK,MAAM,SAAS,QAAQ,EAAG,qBAAoB,GAAG,YAAY,CAAC,KAAK,MAAM;AAAA,EAChG;AAEA,MAAI,CAAC,MAAM,QAAQ,MAAM,SAAS,GAAG;AACnC,WAAO,KAAK,2BAA2B;AAAA,EACzC,OAAO;AACL,UAAM,UAAU,QAAQ,CAAC,GAAG,MAAM;AAChC,UACE,CAAC,SAAS,CAAC,KACX,EAAE,SAAS,cACX,CAAC,SAAS,EAAE,QAAQ,KACpB,OAAO,EAAE,SAAS,SAAS,UAC3B;AACA,eAAO,KAAK,aAAa,CAAC,+CAA+C;AAAA,MAC3E;AAAA,IACF,CAAC;AAAA,EACH;AAEA,MAAI,MAAM,UAAU,QAAW;AAC7B,QAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,GAAG;AAC/B,aAAO,KAAK,oCAAoC;AAAA,IAClD,OAAO;AACL,YAAM,MAAM,QAAQ,CAAC,GAAG,MAAM;AAC5B,YAAI,CAAC,SAAS,CAAC,KAAK,OAAO,EAAE,SAAS,YAAY,OAAO,EAAE,SAAS,UAAU;AAC5E,iBAAO,KAAK,SAAS,CAAC,iDAA4C;AAAA,QACpE;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA;AAAA,IACE,MAAM;AAAA,IACN;AAAA,IACA;AAAA,MACE,CAAC,UAAU,gBAAgB,aAAa;AAAA,MACxC,CAAC,iBAAiB,gBAAgB,aAAa;AAAA,MAC/C,CAAC,WAAW,UAAU,QAAQ;AAAA,MAC9B,CAAC,gBAAgB,CAAC,MAAM,OAAO,MAAM,WAAW,SAAS;AAAA,MACzD,CAAC,gBAAgB,CAAC,MAAM,OAAO,MAAM,WAAW,SAAS;AAAA,MACzD,CAAC,SAAS,gBAAgB,aAAa;AAAA,IACzC;AAAA,IACA;AAAA,EACF;AACA,MAAI,SAAS,MAAM,OAAO,GAAG;AAC3B,QAAI,EAAE,aAAa,MAAM,SAAU,QAAO,KAAK,+CAA+C;AAC9F,QACE,MAAM,QAAQ,mBAAmB,UACjC,OAAO,MAAM,QAAQ,mBAAmB,WACxC;AACA,aAAO,KAAK,uDAAuD;AAAA,IACrE;AAAA,EACF;AAEA;AAAA,IACE,MAAM;AAAA,IACN;AAAA,IACA;AAAA,MACE,CAAC,OAAO,gBAAgB,aAAa;AAAA,MACrC,CAAC,aAAa,gBAAgB,aAAa;AAAA,MAC3C,CAAC,cAAc,gBAAgB,aAAa;AAAA,MAC5C,CAAC,oBAAoB,gBAAgB,aAAa;AAAA,MAClD,CAAC,cAAc,gBAAgB,aAAa;AAAA,MAC5C,CAAC,eAAe,gBAAgB,aAAa;AAAA,MAC7C,CAAC,UAAU,gBAAgB,aAAa;AAAA,IAC1C;AAAA,IACA;AAAA,EACF;AAEA;AAAA,IACE,MAAM;AAAA,IACN;AAAA,IACA;AAAA,MACE,CAAC,cAAc,gBAAgB,aAAa;AAAA,MAC5C,CAAC,WAAW,gBAAgB,aAAa;AAAA,MACzC,CAAC,kBAAkB,gBAAgB,aAAa;AAAA,IAClD;AAAA,IACA;AAAA,EACF;AAEA;AAAA,IACE,MAAM;AAAA,IACN;AAAA,IACA;AAAA,MACE;AAAA,QACE;AAAA,QACA,CAAC,MAAM,OAAO,MAAM,YAAY,CAAC,OAAO,MAAM,KAAK,MAAM,CAAC,CAAC;AAAA,QAC3D;AAAA,MACF;AAAA,MACA;AAAA,QACE;AAAA,QACA,CAAC,MAAM,iBAAiB,SAAS,CAAmB;AAAA,QACpD,UAAU,iBAAiB,KAAK,GAAG,CAAC;AAAA,MACtC;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACA,MACE,SAAS,MAAM,UAAU,KACzB,MAAM,WAAW,QAAQ,UACzB,OAAO,MAAM,WAAW,QAAQ,UAChC;AACA,WAAO,KAAK,6CAA6C;AAAA,EAC3D;AAGA,MAAI,MAAM,QAAQ,MAAM,QAAQ,KAAK,SAAS,MAAM,UAAU,GAAG;AAC/D,QAAI,MAAM,SAAS,WAAW,KAAK,OAAO,MAAM,WAAW,QAAQ,UAAU;AAC3E,aAAO,KAAK,iDAAiD;AAAA,IAC/D;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,kBACd,OACA,UAAU,gBACoB;AAC9B,QAAM,SAAS,oBAAoB,KAAK;AACxC,MAAI,OAAO,SAAS,GAAG;AACrB,UAAM,IAAI,MAAM,WAAW,OAAO;AAAA,IAAQ,OAAO,KAAK,MAAM,CAAC,EAAE;AAAA,EACjE;AACF;AAEO,SAAS,cAAc,OAAsC;AAClE,SAAO,oBAAoB,KAAK,EAAE,WAAW;AAC/C;","names":[]}
|