@valerypopoff/rivet2-evaluations 2.3.0 → 2.4.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.
@@ -36,13 +36,17 @@ __export(index_exports, {
36
36
  areEvaluationDataTypesCompatible: () => areEvaluationDataTypesCompatible,
37
37
  assertEvaluationDatasetSnapshot: () => assertEvaluationDatasetSnapshot,
38
38
  assertEvaluationDatasetValuesMatchDeclaredTypes: () => assertEvaluationDatasetValuesMatchDeclaredTypes,
39
+ assertEvaluationRecordingArtifact: () => assertEvaluationRecordingArtifact,
39
40
  assertPortableJson: () => assertPortableJson,
40
41
  canonicalStringify: () => canonicalStringify,
41
42
  canonicalizePortableJson: () => canonicalizePortableJson,
42
43
  clonePortableJson: () => clonePortableJson,
44
+ createEmptyEvaluationLibrary: () => createEmptyEvaluationLibrary,
43
45
  createEmptyEvaluationProjectData: () => createEmptyEvaluationProjectData,
44
46
  createEmptyMetrics: () => createEmptyMetrics,
45
47
  createEvaluationBaselineSnapshot: () => createEvaluationBaselineSnapshot,
48
+ createEvaluationRunPlan: () => createEvaluationRunPlan,
49
+ createEvaluationRunShell: () => createEvaluationRunShell,
46
50
  deserializeEvaluationDatasetJson: () => deserializeEvaluationDatasetJson,
47
51
  deserializeEvaluationProjectData: () => deserializeEvaluationProjectData,
48
52
  deserializeEvaluationProjectDataFromString: () => deserializeEvaluationProjectDataFromString,
@@ -50,6 +54,7 @@ __export(index_exports, {
50
54
  evaluateAssertion: () => evaluateAssertion,
51
55
  evaluateThresholdResults: () => evaluateThresholdResults,
52
56
  finalizeEvaluationRecordingRetention: () => finalizeEvaluationRecordingRetention,
57
+ finalizeEvaluationRun: () => finalizeEvaluationRun,
53
58
  fingerprint: () => fingerprint,
54
59
  fingerprintEvaluationDataset: () => fingerprintEvaluationDataset,
55
60
  getEvaluationSuiteMode: () => getEvaluationSuiteMode,
@@ -61,11 +66,15 @@ __export(index_exports, {
61
66
  localizeEvaluationDataset: () => localizeEvaluationDataset,
62
67
  normalizeEvaluationAggregate: () => normalizeEvaluationAggregate,
63
68
  normalizeEvaluationBaselineSnapshot: () => normalizeEvaluationBaselineSnapshot,
69
+ normalizeEvaluationLibrary: () => normalizeEvaluationLibrary,
64
70
  normalizeEvaluationRun: () => normalizeEvaluationRun,
65
71
  normalizeEvaluationTrial: () => normalizeEvaluationTrial,
66
72
  portableEvaluationDataTypes: () => portableEvaluationDataTypes,
73
+ preserveEvaluationRunName: () => preserveEvaluationRunName,
74
+ reconcileEvaluationRunSnapshots: () => reconcileEvaluationRunSnapshots,
67
75
  runEvaluationCases: () => runEvaluationCases,
68
76
  runEvaluationSuite: () => runEvaluationSuite,
77
+ runEvaluationTrial: () => runEvaluationTrial,
69
78
  runEvaluationWorkPool: () => runEvaluationWorkPool,
70
79
  serializeEvaluationDatasetJson: () => serializeEvaluationDatasetJson,
71
80
  serializeEvaluationProjectData: () => serializeEvaluationProjectData,
@@ -178,13 +187,29 @@ function fingerprintEvaluationDataset(value) {
178
187
  return fingerprint({ datasetId: value.id, fields: value.fields, cases: value.cases });
179
188
  }
180
189
  function assertEvaluationDatasetSnapshot(snapshot) {
181
- if (snapshot.dataset.projectId !== void 0 && snapshot.dataset.projectId !== snapshot.projectId) {
190
+ if (typeof snapshot.projectId !== "string" || snapshot.projectId.length === 0 || typeof snapshot.fingerprint !== "string" || snapshot.fingerprint.length === 0 || typeof snapshot.createdAt !== "string" || !Number.isFinite(Date.parse(snapshot.createdAt))) {
191
+ throw new Error("An evaluation dataset snapshot has invalid identity or creation time.");
192
+ }
193
+ if (snapshot.dataset.projectId !== snapshot.projectId) {
182
194
  throw new Error("An evaluation dataset snapshot must belong to its project.");
183
195
  }
184
196
  if (fingerprintEvaluationDataset(snapshot.dataset) !== snapshot.fingerprint) {
185
197
  throw new Error("An evaluation dataset snapshot fingerprint must match its dataset ID, fields, and cases.");
186
198
  }
187
199
  }
200
+ function assertEvaluationRecordingArtifact(value) {
201
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
202
+ throw new Error("An evaluation recording artifact must be an object.");
203
+ }
204
+ const artifact = value;
205
+ if (typeof artifact.projectId !== "string" || artifact.projectId.length === 0 || typeof artifact.runId !== "string" || artifact.runId.length === 0 || typeof artifact.trialId !== "string" || artifact.trialId.length === 0 || typeof artifact.serialized !== "string" || typeof artifact.createdAt !== "string" || !Number.isFinite(Date.parse(artifact.createdAt))) {
206
+ throw new Error("An evaluation recording artifact has invalid identity, payload, or creation time.");
207
+ }
208
+ const reference = artifact.reference;
209
+ if (typeof reference !== "object" || reference === null || typeof reference.id !== "string" || reference.id.length === 0 || !["temporary", "failure", "baseline", "retained"].includes(reference.retention ?? "") || reference.expiresAt !== void 0 && (typeof reference.expiresAt !== "string" || !Number.isFinite(Date.parse(reference.expiresAt)))) {
210
+ throw new Error("An evaluation recording artifact has an invalid retention reference.");
211
+ }
212
+ }
188
213
 
189
214
  // src/assertions.ts
190
215
  function readQuotedPathKey(source, start) {
@@ -680,6 +705,12 @@ function isRunExecutionStatus(value) {
680
705
  function isTerminalRunExecutionStatus(status) {
681
706
  return status === "completed" || status === "canceled" || status === "error";
682
707
  }
708
+ function reconcileRecordingReference(primary, secondary) {
709
+ if (!primary) return secondary;
710
+ if (!secondary) return primary;
711
+ const rank = (retention) => retention === "temporary" ? 0 : retention === "failure" ? 1 : 2;
712
+ return rank(secondary.retention) > rank(primary.retention) ? secondary : primary;
713
+ }
683
714
  function shouldReplaceEvaluationRun(existing, incoming) {
684
715
  if (!existing) return true;
685
716
  const existingRevision = existing.revision ?? 0;
@@ -687,6 +718,64 @@ function shouldReplaceEvaluationRun(existing, incoming) {
687
718
  if (incomingRevision !== existingRevision) return incomingRevision > existingRevision;
688
719
  return !isTerminalRunExecutionStatus(existing.executionStatus) || isTerminalRunExecutionStatus(incoming.executionStatus);
689
720
  }
721
+ function preserveEvaluationRunName(existing, incoming) {
722
+ return (existing == null ? void 0 : existing.name) !== void 0 && incoming.name === void 0 ? { ...incoming, name: existing.name } : incoming;
723
+ }
724
+ function reconcileEvaluationRunSnapshots(existing, incoming) {
725
+ if (!existing) return incoming;
726
+ const existingRevision = existing.revision ?? 0;
727
+ const incomingRevision = incoming.revision ?? 0;
728
+ if (incomingRevision < existingRevision) return existing;
729
+ if (incomingRevision > existingRevision) return preserveEvaluationRunName(existing, incoming);
730
+ if (!shouldReplaceEvaluationRun(existing, incoming)) return existing;
731
+ const existingTerminal = isTerminalRunExecutionStatus(existing.executionStatus);
732
+ const incomingTerminal = isTerminalRunExecutionStatus(incoming.executionStatus);
733
+ const existingTrials = existing.trials ?? [];
734
+ const incomingTrials = incoming.trials ?? [];
735
+ const incomingIsPrimary = incomingTerminal !== existingTerminal ? incomingTerminal : incomingTrials.length >= existingTrials.length;
736
+ const primary = preserveEvaluationRunName(existing, incomingIsPrimary ? incoming : existing);
737
+ const secondary = incomingIsPrimary ? existing : incoming;
738
+ const primaryTrials = primary.trials ?? [];
739
+ const secondaryTrialList = secondary.trials ?? [];
740
+ const name = existing.name ?? incoming.name;
741
+ const primaryTrialIds = new Set(primaryTrials.map((trial) => trial.id));
742
+ const secondaryTrials = new Map(secondaryTrialList.map((trial) => [trial.id, trial]));
743
+ return {
744
+ ...primary,
745
+ trials: [
746
+ ...primaryTrials.map((trial) => {
747
+ const other = secondaryTrials.get(trial.id);
748
+ if (!other) return trial;
749
+ const secondaryObservations = new Map(
750
+ (other.observations ?? []).map((observation) => [observation.id, observation])
751
+ );
752
+ const primaryObservationIds = new Set((trial.observations ?? []).map((observation) => observation.id));
753
+ const recording = reconcileRecordingReference(trial.recording, other.recording);
754
+ return {
755
+ ...trial,
756
+ ...recording === void 0 ? {} : { recording },
757
+ observations: [
758
+ ...(trial.observations ?? []).map((observation) => {
759
+ const otherObservation = secondaryObservations.get(observation.id);
760
+ const observationRecording = reconcileRecordingReference(
761
+ observation.recording,
762
+ otherObservation == null ? void 0 : otherObservation.recording
763
+ );
764
+ return observationRecording === observation.recording ? observation : {
765
+ ...observation,
766
+ ...observationRecording === void 0 ? {} : { recording: observationRecording }
767
+ };
768
+ }),
769
+ ...(other.observations ?? []).filter((observation) => !primaryObservationIds.has(observation.id))
770
+ ]
771
+ };
772
+ }),
773
+ ...secondaryTrialList.filter((trial) => !primaryTrialIds.has(trial.id))
774
+ ],
775
+ ...name === void 0 ? {} : { name },
776
+ warnings: [.../* @__PURE__ */ new Set([...primary.warnings ?? [], ...secondary.warnings ?? []])]
777
+ };
778
+ }
690
779
  function isPurpose(value) {
691
780
  return value === "evaluation" || value === "execution-benchmark";
692
781
  }
@@ -797,6 +886,11 @@ function normalizeEvaluationAggregate(value, trials) {
797
886
  if (!isRecord(value)) throw new Error("Evaluation aggregate must be an object.");
798
887
  const normalized = structuredClone(value);
799
888
  if (!isNormalizedScore(normalized.meanScore)) delete normalized.meanScore;
889
+ if (!isNormalizedScore(normalized.medianScore)) delete normalized.medianScore;
890
+ if (!isNormalizedScore(normalized.p95Score)) delete normalized.p95Score;
891
+ if (typeof normalized.medianLatencyMs !== "number" || !Number.isFinite(normalized.medianLatencyMs) || normalized.medianLatencyMs < 0) {
892
+ delete normalized.medianLatencyMs;
893
+ }
800
894
  if (trials) {
801
895
  const completed = trials.filter((trial) => trial.executionStatus === "completed");
802
896
  const evaluated2 = completed.filter((trial) => trial.qualityStatus === "passed" || trial.qualityStatus === "failed");
@@ -899,6 +993,10 @@ function normalizeEvaluationRun(value) {
899
993
  } : value.provenance;
900
994
  const normalized = structuredClone(value);
901
995
  delete normalized.verdict;
996
+ const name = typeof value.name === "string" && value.name.trim().length > 0 ? value.name.trim() : void 0;
997
+ delete normalized.name;
998
+ const requestedTrialCount = typeof value.requestedTrialCount === "number" && Number.isSafeInteger(value.requestedTrialCount) && value.requestedTrialCount >= 0 ? value.requestedTrialCount : void 0;
999
+ delete normalized.requestedTrialCount;
902
1000
  const aggregate2 = value.aggregate === void 0 ? void 0 : normalizeEvaluationAggregate(value.aggregate, trials);
903
1001
  return {
904
1002
  ...normalized,
@@ -918,6 +1016,8 @@ function normalizeEvaluationRun(value) {
918
1016
  provenance,
919
1017
  trials,
920
1018
  thresholdResults: Array.isArray(value.thresholdResults) ? structuredClone(value.thresholdResults) : [],
1019
+ ...name === void 0 ? {} : { name },
1020
+ ...requestedTrialCount === void 0 ? {} : { requestedTrialCount },
921
1021
  ...aggregate2 === void 0 ? {} : { aggregate: accountingStatus === "partial" ? withoutAuthoritativeCost(aggregate2) : aggregate2 }
922
1022
  };
923
1023
  }
@@ -1036,7 +1136,7 @@ async function runEvaluationWorkPool(options) {
1036
1136
  const work = options.work[index];
1037
1137
  const result = await options.execute(work, index);
1038
1138
  results[index] = result;
1039
- (_b = options.onSettled) == null ? void 0 : _b.call(options, result, index, results);
1139
+ await ((_b = options.onSettled) == null ? void 0 : _b.call(options, result, index, results));
1040
1140
  }
1041
1141
  };
1042
1142
  await Promise.all(Array.from({ length: Math.min(options.concurrency, options.work.length) }, worker));
@@ -1316,9 +1416,7 @@ function validateSuite(project, suite, dataset, purpose) {
1316
1416
  for (const assertion of suite.assertions) {
1317
1417
  const outputId = getEvaluationTopLevelOutputId(assertion.outputPath);
1318
1418
  if (assertion.outputPath.trim() !== "$" && outputId === void 0) {
1319
- throw new Error(
1320
- `Assertion "${assertion.name}" output path must start with a named target Graph Output.`
1321
- );
1419
+ throw new Error(`Assertion "${assertion.name}" output path must start with a named target Graph Output.`);
1322
1420
  }
1323
1421
  if (outputId !== void 0 && !graphOutputIds.includes(outputId)) {
1324
1422
  throw new Error(`Assertion "${assertion.name}" references missing target output "${outputId}".`);
@@ -1697,9 +1795,9 @@ async function runWithTimeout(run, timeoutMs, outer) {
1697
1795
  outer == null ? void 0 : outer.removeEventListener("abort", abort);
1698
1796
  }
1699
1797
  }
1700
- async function runTrial(input) {
1798
+ async function runEvaluationTrial(input) {
1701
1799
  var _a, _b, _c, _d;
1702
- const { project, suite, dataset, purpose, testCase, caseIndex, trialIndex, runId, runGraph, signal } = input;
1800
+ const { project, suite, dataset, purpose, testCase, caseIndex, trialIndex, runId, runGraph, signal, trialId } = input;
1703
1801
  const evaluationMode = getEvaluationSuiteMode(suite);
1704
1802
  const { inputs, expected } = resolveCaseInputs(suite, dataset, testCase, purpose);
1705
1803
  const seed = deriveSeed((_a = suite.configuration) == null ? void 0 : _a.seed, testCase.id, trialIndex);
@@ -1745,7 +1843,7 @@ async function runTrial(input) {
1745
1843
  const executionStatus2 = "canceled";
1746
1844
  const qualityStatus2 = "not-evaluated";
1747
1845
  return {
1748
- id: makeId("trial"),
1846
+ id: trialId ?? makeId("trial"),
1749
1847
  caseId: testCase.id,
1750
1848
  caseName: testCase.name,
1751
1849
  caseIndex,
@@ -1873,7 +1971,7 @@ async function runTrial(input) {
1873
1971
  const executionStatus2 = "canceled";
1874
1972
  const qualityStatus2 = "not-evaluated";
1875
1973
  return {
1876
- id: makeId("trial"),
1974
+ id: trialId ?? makeId("trial"),
1877
1975
  caseId: testCase.id,
1878
1976
  caseName: testCase.name,
1879
1977
  caseIndex,
@@ -1958,7 +2056,7 @@ async function runTrial(input) {
1958
2056
  }
1959
2057
  const totalMetrics = mergeMetrics(targetMetrics, evaluatorMetrics);
1960
2058
  return {
1961
- id: makeId("trial"),
2059
+ id: trialId ?? makeId("trial"),
1962
2060
  caseId: testCase.id,
1963
2061
  caseName: testCase.name,
1964
2062
  caseIndex,
@@ -1984,6 +2082,13 @@ function percentile(values, percentage) {
1984
2082
  const ordered = values.slice().sort((left, right) => left - right);
1985
2083
  return ordered[Math.min(ordered.length - 1, Math.ceil(percentage / 100 * ordered.length) - 1)];
1986
2084
  }
2085
+ function median(values) {
2086
+ if (values.length === 0) return 0;
2087
+ const ordered = values.slice().sort((left, right) => left - right);
2088
+ const upperIndex = Math.floor(ordered.length / 2);
2089
+ if (ordered.length % 2 !== 0) return ordered[upperIndex];
2090
+ return (ordered[upperIndex - 1] + ordered[upperIndex]) / 2;
2091
+ }
1987
2092
  function meanObservationScore(observations) {
1988
2093
  let weightedScore = 0;
1989
2094
  let totalWeight = 0;
@@ -2058,6 +2163,7 @@ function aggregate(trials, evaluationMode = "pass-fail") {
2058
2163
  const scoredTrialCount = cases.reduce((sum, testCase) => sum + (testCase.scoredTrialCount ?? 0), 0);
2059
2164
  const scoringCaseMeans = cases.map((testCase) => testCase.meanScore).filter((score) => score !== void 0);
2060
2165
  const scoringMeanScore = scoringCaseMeans.length === 0 ? void 0 : scoringCaseMeans.reduce((sum, score) => sum + score, 0) / scoringCaseMeans.length;
2166
+ const incurredLatencies = incurred.map((trial) => trial.totalMetrics.durationMs);
2061
2167
  const aggregateValue = {
2062
2168
  trialCount: trials.length,
2063
2169
  evaluatedTrialCount: evaluated.length,
@@ -2071,13 +2177,15 @@ function aggregate(trials, evaluationMode = "pass-fail") {
2071
2177
  ...evaluationMode === "scoring" ? {
2072
2178
  scoredTrialCount,
2073
2179
  missingScoreTrialCount: trials.length - scoredTrialCount,
2074
- ...scoringMeanScore === void 0 ? {} : { meanScore: scoringMeanScore }
2180
+ ...scoringMeanScore === void 0 ? {} : { meanScore: scoringMeanScore },
2181
+ ...scoringCaseMeans.length === 0 ? {} : {
2182
+ medianScore: median(scoringCaseMeans),
2183
+ p95Score: percentile(scoringCaseMeans, 95)
2184
+ }
2075
2185
  } : meanScore === void 0 ? {} : { meanScore },
2076
- averageLatencyMs: incurred.length === 0 ? 0 : incurred.reduce((sum, trial) => sum + trial.totalMetrics.durationMs, 0) / incurred.length,
2077
- p95LatencyMs: percentile(
2078
- incurred.map((trial) => trial.totalMetrics.durationMs),
2079
- 95
2080
- ),
2186
+ averageLatencyMs: incurred.length === 0 ? 0 : incurredLatencies.reduce((sum, durationMs) => sum + durationMs, 0) / incurred.length,
2187
+ medianLatencyMs: median(incurredLatencies),
2188
+ p95LatencyMs: percentile(incurredLatencies, 95),
2081
2189
  ...incurred.length === 0 || totalCostUsd === void 0 ? {} : { totalCostUsd, averageCostUsd: totalCostUsd / incurred.length },
2082
2190
  targetErrorRate: settled.length === 0 ? 0 : errors.length / settled.length,
2083
2191
  evaluatorErrorRate: settled.length === 0 ? 0 : settled.filter(
@@ -2405,8 +2513,8 @@ function createProvenance(project, suite, dataset, options) {
2405
2513
  accountingComplete: true
2406
2514
  };
2407
2515
  }
2408
- async function runEvaluationSuite(options) {
2409
- var _a, _b, _c, _d, _e;
2516
+ function createEvaluationRunPlan(options) {
2517
+ var _a;
2410
2518
  const suite = resolveSuite(options.evaluationData, options.suiteId);
2411
2519
  const purpose = options.purpose ?? "evaluation";
2412
2520
  const evaluationMode = getEvaluationSuiteMode(suite);
@@ -2416,6 +2524,110 @@ async function runEvaluationSuite(options) {
2416
2524
  );
2417
2525
  }
2418
2526
  validateSuite(options.project, suite, options.dataset, purpose);
2527
+ const trialCount = Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.trialCount) ?? 1));
2528
+ const work = options.dataset.cases.filter((testCase) => testCase.enabled !== false).flatMap(
2529
+ (testCase, caseIndex) => Array.from({ length: trialCount }, (_, trialIndex) => ({ testCase, caseIndex, trialIndex }))
2530
+ );
2531
+ return { suite, purpose, evaluationMode, work };
2532
+ }
2533
+ function finalizeEvaluationRun(options) {
2534
+ var _a, _b;
2535
+ const run = options.run;
2536
+ const evaluationMode = getEvaluationSuiteMode(options.suite);
2537
+ run.trials = options.trials.slice();
2538
+ run.completedAt = options.completedAt ?? (/* @__PURE__ */ new Date()).toISOString();
2539
+ const outcome = aggregate(run.trials, evaluationMode);
2540
+ run.aggregate = outcome.aggregate;
2541
+ run.provenance.accountingComplete = !run.trials.some((trial) => trial.totalMetrics.hasUnknownCost);
2542
+ run.accountingStatus = run.provenance.accountingComplete ? "complete" : "partial";
2543
+ if (run.accountingStatus === "partial" && !run.warnings.includes("Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated.")) {
2544
+ run.warnings.push("Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated.");
2545
+ }
2546
+ if (options.canceled) {
2547
+ run.executionStatus = "canceled";
2548
+ run.qualityStatus = "not-evaluated";
2549
+ run.qualityReason = { code: "canceled", message: "The evaluation run was canceled." };
2550
+ return run;
2551
+ }
2552
+ if (run.purpose === "execution-benchmark") {
2553
+ run.executionStatus = "completed";
2554
+ run.qualityStatus = "not-evaluated";
2555
+ run.qualityReason = { code: "benchmark", message: "This run measured execution without evaluating output quality." };
2556
+ return run;
2557
+ }
2558
+ if (evaluationMode === "scoring") {
2559
+ run.executionStatus = "completed";
2560
+ if ((outcome.aggregate.missingScoreTrialCount ?? run.trials.length) > 0) {
2561
+ run.qualityStatus = "unable-to-evaluate";
2562
+ run.qualityReason = { code: "scores-incomplete", message: "One or more requested trials did not produce a usable score. Available averages are shown with coverage." };
2563
+ run.warnings.push(`Score coverage is ${outcome.aggregate.scoredTrialCount ?? 0} of ${outcome.aggregate.trialCount} requested trials.`);
2564
+ } else if ((outcome.aggregate.scoredTrialCount ?? 0) > 0) {
2565
+ run.qualityStatus = "scored";
2566
+ run.qualityReason = { code: "scores-complete", message: "Every requested trial produced a score. Overall score is the equal-weight average of case averages." };
2567
+ } else {
2568
+ run.qualityStatus = "unable-to-evaluate";
2569
+ run.qualityReason = { code: "scores-incomplete", message: "No requested trial produced a usable score." };
2570
+ }
2571
+ return run;
2572
+ }
2573
+ const baseline = options.baseline ?? options.evaluationData.baselines.find((candidate) => candidate.suiteId === options.suite.id);
2574
+ const usableBaseline = baseline && baselineCompatible(run.provenance, baseline) ? baseline : void 0;
2575
+ if (baseline && !usableBaseline && !run.warnings.includes("The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed.")) {
2576
+ run.warnings.push("The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed.");
2577
+ }
2578
+ run.thresholdResults = evaluateThresholdResults(outcome.aggregate, options.suite.thresholds, usableBaseline);
2579
+ const hasPassRateThreshold = ((_a = options.suite.thresholds) == null ? void 0 : _a.some((threshold) => threshold.metric === "pass-rate")) ?? false;
2580
+ const hasTargetErrorRateThreshold = ((_b = options.suite.thresholds) == null ? void 0 : _b.some((threshold) => threshold.metric === "target-error-rate")) ?? false;
2581
+ const hasUngovernedCheckFailure = !hasPassRateThreshold && run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "failed");
2582
+ const hasUngovernedTargetError = !hasTargetErrorRateThreshold && run.trials.some((trial) => trial.executionStatus === "error");
2583
+ const hasThresholdFailure = run.thresholdResults.some((result) => result.status === "failed");
2584
+ const hasUnavailableEvidence = run.trials.some((trial) => trial.qualityStatus === "unable-to-evaluate") || run.thresholdResults.some((result) => result.status === "unavailable");
2585
+ const hasAuthoritativePass = run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "passed") || run.thresholdResults.some((result) => result.status === "passed");
2586
+ run.executionStatus = "completed";
2587
+ if (hasUngovernedCheckFailure || hasUngovernedTargetError || hasThresholdFailure) {
2588
+ run.qualityStatus = "failed";
2589
+ run.qualityReason = { code: hasThresholdFailure ? "thresholds-failed" : "checks-failed", message: hasThresholdFailure ? "One or more required aggregate thresholds failed." : "One or more target executions or required quality checks failed." };
2590
+ } else if (hasUnavailableEvidence) {
2591
+ run.qualityStatus = "unable-to-evaluate";
2592
+ run.qualityReason = { code: run.thresholdResults.some((result) => result.status === "unavailable") ? "required-metric-unavailable" : "required-check-error", message: run.thresholdResults.some((result) => result.status === "unavailable") ? "A required aggregate metric could not be evaluated." : "A required quality check could not be evaluated." };
2593
+ } else if (hasAuthoritativePass) {
2594
+ run.qualityStatus = "passed";
2595
+ run.qualityReason = { code: run.thresholdResults.length > 0 ? "thresholds-passed" : "checks-passed", message: run.thresholdResults.length > 0 ? "All required quality checks and aggregate thresholds passed." : "All required quality checks passed." };
2596
+ } else {
2597
+ run.qualityStatus = "not-evaluated";
2598
+ run.qualityReason = { code: "no-completed-trials", message: "No completed trial produced an authoritative quality result." };
2599
+ }
2600
+ return run;
2601
+ }
2602
+ function createEvaluationRunShell(options) {
2603
+ const plan = createEvaluationRunPlan(options);
2604
+ const runId = options.runId ?? makeId("evaluation");
2605
+ const run = {
2606
+ version: 2,
2607
+ id: runId,
2608
+ projectId: options.project.metadata.id,
2609
+ suiteId: plan.suite.id,
2610
+ suiteName: plan.suite.name,
2611
+ revision: 0,
2612
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2613
+ purpose: plan.purpose,
2614
+ evaluationMode: plan.evaluationMode,
2615
+ executionStatus: "running",
2616
+ qualityStatus: "not-evaluated",
2617
+ qualityReason: { code: "in-progress", message: "The run is still in progress." },
2618
+ accountingStatus: "complete",
2619
+ provenance: createProvenance(options.project, plan.suite, options.dataset, options),
2620
+ trials: [],
2621
+ thresholdResults: [],
2622
+ warnings: [],
2623
+ requestedTrialCount: plan.work.length
2624
+ };
2625
+ return { run, plan };
2626
+ }
2627
+ async function runEvaluationSuite(options) {
2628
+ var _a, _b;
2629
+ const plan = createEvaluationRunPlan(options);
2630
+ const { suite, purpose, evaluationMode } = plan;
2419
2631
  const runId = options.runId ?? makeId("evaluation");
2420
2632
  const run = {
2421
2633
  version: 2,
@@ -2436,27 +2648,61 @@ async function runEvaluationSuite(options) {
2436
2648
  thresholdResults: [],
2437
2649
  warnings: []
2438
2650
  };
2439
- const trialCount = Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.trialCount) ?? 1));
2440
2651
  const concurrency = Math.min(
2441
2652
  MAX_CONCURRENCY,
2442
- Math.max(1, Math.floor(((_b = suite.configuration) == null ? void 0 : _b.concurrency) ?? DEFAULT_CONCURRENCY))
2653
+ Math.max(1, Math.floor(((_a = suite.configuration) == null ? void 0 : _a.concurrency) ?? DEFAULT_CONCURRENCY))
2443
2654
  );
2444
- const work = options.dataset.cases.filter((testCase) => testCase.enabled !== false).flatMap(
2445
- (testCase, caseIndex) => Array.from({ length: trialCount }, (_, trialIndex) => ({ testCase, caseIndex, trialIndex }))
2446
- );
2447
- const publish = () => {
2448
- var _a2;
2655
+ const work = plan.work;
2656
+ run.requestedTrialCount = work.length;
2657
+ const settledTrials = Array.from({ length: work.length });
2658
+ let settledTrialCount = 0;
2659
+ const nextRevision = () => {
2449
2660
  run.revision = (run.revision ?? 0) + 1;
2450
- (_a2 = options.onUpdate) == null ? void 0 : _a2.call(options, structuredClone(run));
2661
+ return run.revision;
2662
+ };
2663
+ const publishLegacySnapshot = () => {
2664
+ if (!options.onUpdate) return;
2665
+ run.trials = settledTrials.filter((trial) => trial !== void 0);
2666
+ options.onUpdate(structuredClone(run));
2451
2667
  };
2452
- publish();
2668
+ const publishStarted = async () => {
2669
+ const revision = nextRevision();
2670
+ if (options.onEvent) {
2671
+ const shell = structuredClone(run);
2672
+ shell.trials = [];
2673
+ await options.onEvent({ type: "run-started", revision, run: shell });
2674
+ }
2675
+ publishLegacySnapshot();
2676
+ };
2677
+ const publishSettledTrial = async (trial) => {
2678
+ var _a2;
2679
+ const revision = nextRevision();
2680
+ await ((_a2 = options.onEvent) == null ? void 0 : _a2.call(options, {
2681
+ type: "trial-settled",
2682
+ revision,
2683
+ runId: run.id,
2684
+ projectId: run.projectId,
2685
+ suiteId: run.suiteId,
2686
+ requestedTrialCount: run.requestedTrialCount ?? work.length,
2687
+ settledTrialCount,
2688
+ trial: structuredClone(trial)
2689
+ }));
2690
+ publishLegacySnapshot();
2691
+ };
2692
+ const publishFinalized = async () => {
2693
+ var _a2;
2694
+ const revision = nextRevision();
2695
+ await ((_a2 = options.onEvent) == null ? void 0 : _a2.call(options, { type: "run-finalized", revision, run: structuredClone(run) }));
2696
+ publishLegacySnapshot();
2697
+ };
2698
+ await publishStarted();
2453
2699
  const workPool = runEvaluationWorkPool({
2454
2700
  work,
2455
2701
  concurrency,
2456
2702
  signal: options.signal,
2457
2703
  execute: async (task) => {
2458
2704
  try {
2459
- return await runTrial({
2705
+ return await runEvaluationTrial({
2460
2706
  ...task,
2461
2707
  project: options.project,
2462
2708
  suite,
@@ -2489,98 +2735,22 @@ async function runEvaluationSuite(options) {
2489
2735
  };
2490
2736
  }
2491
2737
  },
2492
- onSettled: (_result, _index, currentResults) => {
2493
- run.trials = currentResults.filter((result) => result !== void 0);
2494
- publish();
2738
+ onSettled: async (result, index) => {
2739
+ settledTrials[index] = result;
2740
+ settledTrialCount += 1;
2741
+ await publishSettledTrial(result);
2495
2742
  }
2496
2743
  });
2497
2744
  const results = await workPool;
2498
- run.trials = results.filter((result) => result !== void 0);
2499
- run.completedAt = (/* @__PURE__ */ new Date()).toISOString();
2500
- const outcome = aggregate(run.trials, evaluationMode);
2501
- run.aggregate = outcome.aggregate;
2502
- run.provenance.accountingComplete = !run.trials.some((trial) => trial.totalMetrics.hasUnknownCost);
2503
- run.accountingStatus = run.provenance.accountingComplete ? "complete" : "partial";
2504
- if (run.accountingStatus === "partial") {
2505
- run.warnings.push(
2506
- "Some provider pricing was unavailable. Cost totals are unavailable, and cost requirements cannot be evaluated."
2507
- );
2508
- }
2509
- if ((_c = options.signal) == null ? void 0 : _c.aborted) {
2510
- run.executionStatus = "canceled";
2511
- run.qualityStatus = "not-evaluated";
2512
- run.qualityReason = { code: "canceled", message: "The evaluation run was canceled." };
2513
- } else if (purpose === "execution-benchmark") {
2514
- run.executionStatus = "completed";
2515
- run.qualityStatus = "not-evaluated";
2516
- run.qualityReason = {
2517
- code: "benchmark",
2518
- message: "This run measured execution without evaluating output quality."
2519
- };
2520
- } else if (evaluationMode === "scoring") {
2521
- run.executionStatus = "completed";
2522
- if ((outcome.aggregate.missingScoreTrialCount ?? run.trials.length) > 0) {
2523
- run.qualityStatus = "unable-to-evaluate";
2524
- run.qualityReason = {
2525
- code: "scores-incomplete",
2526
- message: "One or more requested trials did not produce a usable score. Available averages are shown with coverage."
2527
- };
2528
- run.warnings.push(
2529
- `Score coverage is ${outcome.aggregate.scoredTrialCount ?? 0} of ${outcome.aggregate.trialCount} requested trials.`
2530
- );
2531
- } else if ((outcome.aggregate.scoredTrialCount ?? 0) > 0) {
2532
- run.qualityStatus = "scored";
2533
- run.qualityReason = {
2534
- code: "scores-complete",
2535
- message: "Every requested trial produced a score. Overall score is the equal-weight average of case averages."
2536
- };
2537
- } else {
2538
- run.qualityStatus = "unable-to-evaluate";
2539
- run.qualityReason = { code: "scores-incomplete", message: "No requested trial produced a usable score." };
2540
- }
2541
- } else {
2542
- const baseline = options.baseline ?? options.evaluationData.baselines.find((candidate) => candidate.suiteId === suite.id);
2543
- const usableBaseline = baseline && baselineCompatible(run.provenance, baseline) ? baseline : void 0;
2544
- if (baseline && !usableBaseline)
2545
- run.warnings.push(
2546
- "The suite baseline is stale because its target, dataset, bindings, or evaluator definition changed."
2547
- );
2548
- run.thresholdResults = evaluateThresholdResults(outcome.aggregate, suite.thresholds, usableBaseline);
2549
- const hasPassRateThreshold = ((_d = suite.thresholds) == null ? void 0 : _d.some((threshold) => threshold.metric === "pass-rate")) ?? false;
2550
- const hasTargetErrorRateThreshold = ((_e = suite.thresholds) == null ? void 0 : _e.some((threshold) => threshold.metric === "target-error-rate")) ?? false;
2551
- const hasUngovernedCheckFailure = !hasPassRateThreshold && run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "failed");
2552
- const hasUngovernedTargetError = !hasTargetErrorRateThreshold && run.trials.some((trial) => trial.executionStatus === "error");
2553
- const hasThresholdFailure = run.thresholdResults.some((result) => result.status === "failed");
2554
- const hasUnavailableEvidence = run.trials.some((trial) => trial.qualityStatus === "unable-to-evaluate") || run.thresholdResults.some((result) => result.status === "unavailable");
2555
- const hasAuthoritativePass = run.trials.some((trial) => trial.executionStatus === "completed" && trial.qualityStatus === "passed") || run.thresholdResults.some((result) => result.status === "passed");
2556
- if (hasUngovernedCheckFailure || hasUngovernedTargetError || hasThresholdFailure) {
2557
- run.qualityStatus = "failed";
2558
- run.qualityReason = {
2559
- code: hasThresholdFailure ? "thresholds-failed" : "checks-failed",
2560
- message: hasThresholdFailure ? "One or more required aggregate thresholds failed." : "One or more target executions or required quality checks failed."
2561
- };
2562
- } else if (hasUnavailableEvidence) {
2563
- run.qualityStatus = "unable-to-evaluate";
2564
- run.qualityReason = {
2565
- code: run.thresholdResults.some((result) => result.status === "unavailable") ? "required-metric-unavailable" : "required-check-error",
2566
- message: run.thresholdResults.some((result) => result.status === "unavailable") ? "A required aggregate metric could not be evaluated." : "A required quality check could not be evaluated."
2567
- };
2568
- } else if (hasAuthoritativePass) {
2569
- run.qualityStatus = "passed";
2570
- run.qualityReason = {
2571
- code: run.thresholdResults.length > 0 ? "thresholds-passed" : "checks-passed",
2572
- message: run.thresholdResults.length > 0 ? "All required quality checks and aggregate thresholds passed." : "All required quality checks passed."
2573
- };
2574
- } else {
2575
- run.qualityStatus = "not-evaluated";
2576
- run.qualityReason = {
2577
- code: "no-completed-trials",
2578
- message: "No completed trial produced an authoritative quality result."
2579
- };
2580
- }
2581
- run.executionStatus = "completed";
2582
- }
2583
- publish();
2745
+ finalizeEvaluationRun({
2746
+ run,
2747
+ trials: results.filter((result) => result !== void 0),
2748
+ suite,
2749
+ evaluationData: options.evaluationData,
2750
+ baseline: options.baseline,
2751
+ canceled: ((_b = options.signal) == null ? void 0 : _b.aborted) === true
2752
+ });
2753
+ await publishFinalized();
2584
2754
  return run;
2585
2755
  }
2586
2756
  async function runEvaluationCases(options) {
@@ -2619,93 +2789,131 @@ function createEvaluationBaselineSnapshot(run) {
2619
2789
  };
2620
2790
  }
2621
2791
 
2622
- // src/store.ts
2623
- var InMemoryEvaluationRunStore = class {
2624
- #runs = /* @__PURE__ */ new Map();
2625
- #datasetSnapshots = /* @__PURE__ */ new Map();
2626
- #recordings = /* @__PURE__ */ new Map();
2627
- async put(run) {
2628
- const normalized = normalizeEvaluationRun(run);
2629
- const key = `${normalized.projectId}/${normalized.id}`;
2630
- const existing = this.#runs.get(key);
2631
- if (!shouldReplaceEvaluationRun(existing, normalized)) return;
2632
- this.#runs.set(key, structuredClone(normalized));
2633
- }
2634
- async get(input) {
2635
- const run = this.#runs.get(`${input.projectId}/${input.runId}`);
2636
- return run ? normalizeEvaluationRun(run) : void 0;
2637
- }
2638
- async list(input) {
2639
- return Array.from(this.#runs.values()).filter(
2640
- (run) => run.projectId === input.projectId && (input.suiteId === void 0 || run.suiteId === input.suiteId)
2641
- ).sort((left, right) => right.startedAt.localeCompare(left.startedAt)).map(normalizeEvaluationRun);
2642
- }
2643
- async delete(input) {
2644
- this.#runs.delete(`${input.projectId}/${input.runId}`);
2645
- for (const [key, artifact] of this.#recordings) {
2646
- if (artifact.projectId === input.projectId && artifact.runId === input.runId) this.#recordings.delete(key);
2792
+ // src/datasetTransfer.ts
2793
+ var DATASET_EXPORT_VERSION = 1;
2794
+ function isRecord2(value) {
2795
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2796
+ }
2797
+ function requireString(value, path) {
2798
+ if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string.`);
2799
+ return value;
2800
+ }
2801
+ function requireText(value, path) {
2802
+ if (typeof value !== "string") throw new Error(`${path} must be a string.`);
2803
+ return value;
2804
+ }
2805
+ function parseFields(value) {
2806
+ if (!Array.isArray(value)) throw new Error("Evaluation dataset fields must be an array.");
2807
+ const ids = /* @__PURE__ */ new Set();
2808
+ return value.map((field, index) => {
2809
+ if (!isRecord2(field)) throw new Error(`fields[${index}] must be an object.`);
2810
+ const id = requireString(field.id, `fields[${index}].id`);
2811
+ if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate field id "${id}".`);
2812
+ ids.add(id);
2813
+ const role = field.role;
2814
+ if (role !== "input" && role !== "expected" && role !== "metadata") {
2815
+ throw new Error(`fields[${index}].role must be input, expected, or metadata.`);
2647
2816
  }
2648
- }
2649
- async putDatasetSnapshot(snapshot) {
2650
- assertEvaluationDatasetSnapshot(snapshot);
2651
- const key = `${snapshot.projectId}/${snapshot.fingerprint}`;
2652
- if (this.#datasetSnapshots.has(key)) return;
2653
- this.#datasetSnapshots.set(key, structuredClone(snapshot));
2654
- }
2655
- async getDatasetSnapshot(input) {
2656
- const snapshot = this.#datasetSnapshots.get(`${input.projectId}/${input.fingerprint}`);
2657
- return snapshot ? structuredClone(snapshot) : void 0;
2658
- }
2659
- async putRecording(artifact) {
2660
- const key = `${artifact.projectId}/${artifact.reference.id}`;
2661
- const existing = this.#recordings.get(key);
2662
- if (existing && (existing.runId !== artifact.runId || existing.trialId !== artifact.trialId)) {
2663
- throw new Error("An evaluation recording ID cannot be reassigned to another run or trial.");
2817
+ if (field.description !== void 0 && typeof field.description !== "string") {
2818
+ throw new Error(`fields[${index}].description must be a string.`);
2664
2819
  }
2665
- const next = structuredClone(artifact);
2666
- if (existing) next.reference = structuredClone(existing.reference);
2667
- this.#recordings.set(key, next);
2668
- }
2669
- async getRecording(input) {
2670
- const artifact = this.#recordings.get(`${input.projectId}/${input.recordingId}`);
2671
- if (!artifact) return void 0;
2672
- if (artifact.reference.retention === "temporary" && artifact.reference.expiresAt && Date.parse(artifact.reference.expiresAt) <= Date.now()) {
2673
- this.#recordings.delete(`${input.projectId}/${input.recordingId}`);
2674
- return void 0;
2820
+ if (field.required !== void 0 && typeof field.required !== "boolean") {
2821
+ throw new Error(`fields[${index}].required must be a boolean.`);
2675
2822
  }
2676
- return structuredClone(artifact);
2677
- }
2678
- async updateRecordingRetention(input) {
2679
- const key = `${input.projectId}/${input.recordingId}`;
2680
- const artifact = this.#recordings.get(key);
2681
- if (!artifact) return;
2682
- artifact.reference = {
2683
- id: artifact.reference.id,
2684
- retention: input.retention,
2685
- ...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt }
2823
+ return {
2824
+ id,
2825
+ name: requireText(field.name, `fields[${index}].name`),
2826
+ dataType: requireString(field.dataType, `fields[${index}].dataType`),
2827
+ role,
2828
+ ...typeof field.description === "string" ? { description: field.description } : {},
2829
+ ...typeof field.required === "boolean" ? { required: field.required } : {}
2686
2830
  };
2687
- }
2688
- async promoteBaseline(input) {
2689
- for (const artifact of this.#recordings.values()) {
2690
- if (artifact.projectId !== input.projectId || artifact.runId !== input.runId) continue;
2691
- artifact.reference = { id: artifact.reference.id, retention: "baseline" };
2831
+ });
2832
+ }
2833
+ function parseCases(value, fieldIds) {
2834
+ if (!Array.isArray(value)) throw new Error("Evaluation dataset cases must be an array.");
2835
+ const ids = /* @__PURE__ */ new Set();
2836
+ return value.map((testCase, index) => {
2837
+ if (!isRecord2(testCase)) throw new Error(`cases[${index}] must be an object.`);
2838
+ const id = requireString(testCase.id, `cases[${index}].id`);
2839
+ if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate case id "${id}".`);
2840
+ ids.add(id);
2841
+ if (!isRecord2(testCase.values)) throw new Error(`cases[${index}].values must be an object.`);
2842
+ for (const [fieldId, fieldValue] of Object.entries(testCase.values)) {
2843
+ if (!fieldIds.has(fieldId)) throw new Error(`cases[${index}].values references unknown field "${fieldId}".`);
2844
+ assertPortableJson(fieldValue, `cases[${index}].values.${fieldId}`);
2845
+ }
2846
+ if (testCase.tags !== void 0 && (!Array.isArray(testCase.tags) || testCase.tags.some((tag) => typeof tag !== "string"))) {
2847
+ throw new Error(`cases[${index}].tags must be an array of strings.`);
2692
2848
  }
2849
+ if (testCase.note !== void 0 && typeof testCase.note !== "string") throw new Error(`cases[${index}].note must be a string.`);
2850
+ if (testCase.enabled !== void 0 && typeof testCase.enabled !== "boolean") throw new Error(`cases[${index}].enabled must be a boolean.`);
2851
+ return {
2852
+ id,
2853
+ name: requireText(testCase.name, `cases[${index}].name`),
2854
+ values: testCase.values,
2855
+ ...typeof testCase.enabled === "boolean" ? { enabled: testCase.enabled } : {},
2856
+ ...Array.isArray(testCase.tags) ? { tags: testCase.tags } : {},
2857
+ ...typeof testCase.note === "string" ? { note: testCase.note } : {}
2858
+ };
2859
+ });
2860
+ }
2861
+ function validateEvaluationDataset(value, scope) {
2862
+ if (!isRecord2(value)) throw new Error("Evaluation dataset must be an object.");
2863
+ const fields = parseFields(value.fields);
2864
+ const importedProjectId = value.projectId === void 0 ? void 0 : requireString(value.projectId, "dataset.projectId");
2865
+ const projectId = (scope == null ? void 0 : scope.projectId) ?? importedProjectId;
2866
+ const dataset = {
2867
+ id: (scope == null ? void 0 : scope.id) ?? requireString(value.id, "dataset.id"),
2868
+ name: requireText(value.name, "dataset.name"),
2869
+ ...projectId === void 0 ? {} : { projectId },
2870
+ fields,
2871
+ cases: parseCases(value.cases, new Set(fields.map((field) => field.id)))
2872
+ };
2873
+ return { ...dataset, contentFingerprint: fingerprintEvaluationDataset(dataset) };
2874
+ }
2875
+ function localizeEvaluationDataset(value, id) {
2876
+ const dataset = validateEvaluationDataset(value, id === void 0 ? void 0 : { id });
2877
+ const { projectId: _legacyProjectId, ...localDataset } = dataset;
2878
+ return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
2879
+ }
2880
+ function validateEvaluationDatasetForTransfer(value, scope) {
2881
+ const dataset = validateEvaluationDataset(value, scope);
2882
+ assertEvaluationDatasetValuesMatchDeclaredTypes(dataset);
2883
+ return dataset;
2884
+ }
2885
+ function serializeEvaluationDatasetJson(dataset) {
2886
+ const validated = validateEvaluationDatasetForTransfer(dataset);
2887
+ return JSON.stringify({ version: DATASET_EXPORT_VERSION, dataset: validated }, null, 2);
2888
+ }
2889
+ function deserializeEvaluationDatasetJson(source, scope) {
2890
+ let parsed;
2891
+ try {
2892
+ parsed = JSON.parse(source);
2893
+ } catch {
2894
+ throw new Error("Evaluation dataset JSON is not valid JSON.");
2693
2895
  }
2694
- };
2896
+ if (!isRecord2(parsed) || parsed.version !== DATASET_EXPORT_VERSION || !isRecord2(parsed.dataset)) {
2897
+ throw new Error(`Expected an evaluation dataset export with version ${DATASET_EXPORT_VERSION}.`);
2898
+ }
2899
+ const dataset = validateEvaluationDatasetForTransfer(parsed.dataset, { id: scope.id });
2900
+ const { projectId: _legacyProjectId, ...localDataset } = dataset;
2901
+ return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
2902
+ }
2695
2903
 
2696
2904
  // src/serialization.ts
2697
2905
  var yaml = __toESM(require("yaml"), 1);
2698
- function isRecord2(value) {
2906
+ function isRecord3(value) {
2699
2907
  return value !== null && typeof value === "object" && !Array.isArray(value);
2700
2908
  }
2701
2909
  function requireRecord(value, path) {
2702
- if (!isRecord2(value)) throw new Error(`${path} must be an object.`);
2910
+ if (!isRecord3(value)) throw new Error(`${path} must be an object.`);
2703
2911
  return value;
2704
2912
  }
2705
- function requireString(value, path) {
2913
+ function requireString2(value, path) {
2706
2914
  if (typeof value !== "string" || value.trim().length === 0) throw new Error(`${path} must be a non-empty string.`);
2707
2915
  }
2708
- function requireText(value, path) {
2916
+ function requireText2(value, path) {
2709
2917
  if (typeof value !== "string") throw new Error(`${path} must be a string.`);
2710
2918
  }
2711
2919
  function requireBoolean(value, path) {
@@ -2747,21 +2955,21 @@ var EVALUATOR_CONTEXT_INPUTS = /* @__PURE__ */ new Set(["case", "inputs", "expec
2747
2955
  function validateSuiteShape(value, index) {
2748
2956
  const path = `evaluations.suites[${index}]`;
2749
2957
  const suite = requireRecord(value, path);
2750
- for (const key of ["id", "targetGraphId", "datasetId"]) requireString(suite[key], `${path}.${key}`);
2751
- requireText(suite.name, `${path}.name`);
2958
+ for (const key of ["id", "targetGraphId", "datasetId"]) requireString2(suite[key], `${path}.${key}`);
2959
+ requireText2(suite.name, `${path}.name`);
2752
2960
  if (suite.evaluationMode !== void 0 && suite.evaluationMode !== "pass-fail" && suite.evaluationMode !== "scoring") {
2753
2961
  throw new Error(`${path}.evaluationMode must be "pass-fail" or "scoring".`);
2754
2962
  }
2755
2963
  for (const [bindingIndex, bindingValue] of requireArray(suite.inputBindings, `${path}.inputBindings`).entries()) {
2756
2964
  const binding = requireRecord(bindingValue, `${path}.inputBindings[${bindingIndex}]`);
2757
- requireString(binding.graphInputId, `${path}.inputBindings[${bindingIndex}].graphInputId`);
2758
- requireString(binding.datasetFieldId, `${path}.inputBindings[${bindingIndex}].datasetFieldId`);
2965
+ requireString2(binding.graphInputId, `${path}.inputBindings[${bindingIndex}].graphInputId`);
2966
+ requireString2(binding.datasetFieldId, `${path}.inputBindings[${bindingIndex}].datasetFieldId`);
2759
2967
  }
2760
2968
  for (const [assertionIndex, assertionValue] of requireArray(suite.assertions, `${path}.assertions`).entries()) {
2761
2969
  const assertionPath = `${path}.assertions[${assertionIndex}]`;
2762
2970
  const assertion = requireRecord(assertionValue, assertionPath);
2763
- for (const key of ["id", "outputPath"]) requireString(assertion[key], `${assertionPath}.${key}`);
2764
- requireText(assertion.name, `${assertionPath}.name`);
2971
+ for (const key of ["id", "outputPath"]) requireString2(assertion[key], `${assertionPath}.${key}`);
2972
+ requireText2(assertion.name, `${assertionPath}.name`);
2765
2973
  if (typeof assertion.operator !== "string" || !ASSERTION_OPERATORS.has(assertion.operator)) {
2766
2974
  throw new Error(`${assertionPath}.operator is not supported.`);
2767
2975
  }
@@ -2770,7 +2978,7 @@ function validateSuiteShape(value, index) {
2770
2978
  if (expected.kind === "literal") {
2771
2979
  if (!Object.hasOwn(expected, "value")) throw new Error(`${assertionPath}.expected.value is required.`);
2772
2980
  } else if (expected.kind === "dataset-field") {
2773
- requireString(expected.fieldId, `${assertionPath}.expected.fieldId`);
2981
+ requireString2(expected.fieldId, `${assertionPath}.expected.fieldId`);
2774
2982
  } else {
2775
2983
  throw new Error(`${assertionPath}.expected.kind must be "literal" or "dataset-field".`);
2776
2984
  }
@@ -2778,8 +2986,8 @@ function validateSuiteShape(value, index) {
2778
2986
  for (const [evaluatorIndex, evaluatorValue] of requireArray(suite.evaluators, `${path}.evaluators`).entries()) {
2779
2987
  const evaluatorPath = `${path}.evaluators[${evaluatorIndex}]`;
2780
2988
  const evaluator = requireRecord(evaluatorValue, evaluatorPath);
2781
- for (const key of ["id", "graphId"]) requireString(evaluator[key], `${evaluatorPath}.${key}`);
2782
- requireText(evaluator.name, `${evaluatorPath}.name`);
2989
+ for (const key of ["id", "graphId"]) requireString2(evaluator[key], `${evaluatorPath}.${key}`);
2990
+ requireText2(evaluator.name, `${evaluatorPath}.name`);
2783
2991
  validateOptionalPrimitive(evaluator, "required", "boolean", evaluatorPath);
2784
2992
  validateOptionalPrimitive(evaluator, "runOnTargetError", "boolean", evaluatorPath);
2785
2993
  validateOptionalPrimitive(evaluator, "scoreWeight", "number", evaluatorPath);
@@ -2790,10 +2998,10 @@ function validateSuiteShape(value, index) {
2790
2998
  ).entries()) {
2791
2999
  const bindingPath = `${evaluatorPath}.inputBindings[${bindingIndex}]`;
2792
3000
  const binding = requireRecord(bindingValue, bindingPath);
2793
- requireString(binding.graphInputId, `${bindingPath}.graphInputId`);
3001
+ requireString2(binding.graphInputId, `${bindingPath}.graphInputId`);
2794
3002
  const source = requireRecord(binding.source, `${bindingPath}.source`);
2795
- if (source.kind === "dataset-field") requireString(source.fieldId, `${bindingPath}.source.fieldId`);
2796
- else if (source.kind === "target-output") requireString(source.outputId, `${bindingPath}.source.outputId`);
3003
+ if (source.kind === "dataset-field") requireString2(source.fieldId, `${bindingPath}.source.fieldId`);
3004
+ else if (source.kind === "target-output") requireString2(source.outputId, `${bindingPath}.source.outputId`);
2797
3005
  else if (source.kind === "context") {
2798
3006
  if (typeof source.context !== "string" || !EVALUATOR_CONTEXT_INPUTS.has(source.context)) {
2799
3007
  throw new Error(`${bindingPath}.source.context is not supported.`);
@@ -2808,7 +3016,7 @@ function validateSuiteShape(value, index) {
2808
3016
  for (const [thresholdIndex, thresholdValue2] of requireArray(suite.thresholds, `${path}.thresholds`).entries()) {
2809
3017
  const thresholdPath = `${path}.thresholds[${thresholdIndex}]`;
2810
3018
  const threshold = requireRecord(thresholdValue2, thresholdPath);
2811
- for (const key of ["id", "metric", "operator"]) requireString(threshold[key], `${thresholdPath}.${key}`);
3019
+ for (const key of ["id", "metric", "operator"]) requireString2(threshold[key], `${thresholdPath}.${key}`);
2812
3020
  requireFiniteNumber(threshold.value, `${thresholdPath}.value`);
2813
3021
  }
2814
3022
  }
@@ -2823,7 +3031,7 @@ function validateSuiteShape(value, index) {
2823
3031
  validateOptionalPrimitive(suite, "description", "string", path);
2824
3032
  if (suite.tags !== void 0) {
2825
3033
  for (const [tagIndex, tag] of requireArray(suite.tags, `${path}.tags`).entries()) {
2826
- requireString(tag, `${path}.tags[${tagIndex}]`);
3034
+ requireString2(tag, `${path}.tags[${tagIndex}]`);
2827
3035
  }
2828
3036
  }
2829
3037
  }
@@ -2834,7 +3042,7 @@ function validateNumericRecord(value, path) {
2834
3042
  function validateBaselineShape(value, index) {
2835
3043
  const path = `evaluations.baselines[${index}]`;
2836
3044
  const baseline = requireRecord(value, path);
2837
- for (const key of ["id", "suiteId", "createdAt"]) requireString(baseline[key], `${path}.${key}`);
3045
+ for (const key of ["id", "suiteId", "createdAt"]) requireString2(baseline[key], `${path}.${key}`);
2838
3046
  validateOptionalPrimitive(baseline, "sourceRunId", "string", path);
2839
3047
  if (baseline.evaluationMode !== void 0 && baseline.evaluationMode !== "pass-fail" && baseline.evaluationMode !== "scoring") {
2840
3048
  throw new Error(`${path}.evaluationMode must be "pass-fail" or "scoring".`);
@@ -2847,14 +3055,14 @@ function validateBaselineShape(value, index) {
2847
3055
  "targetFingerprint",
2848
3056
  "executionMode"
2849
3057
  ]) {
2850
- requireString(provenance[key], `${path}.provenance.${key}`);
3058
+ requireString2(provenance[key], `${path}.provenance.${key}`);
2851
3059
  }
2852
3060
  const evaluatorFingerprints = requireRecord(
2853
3061
  provenance.evaluatorFingerprints,
2854
3062
  `${path}.provenance.evaluatorFingerprints`
2855
3063
  );
2856
3064
  for (const [key, fingerprint2] of Object.entries(evaluatorFingerprints)) {
2857
- requireString(fingerprint2, `${path}.provenance.evaluatorFingerprints.${key}`);
3065
+ requireString2(fingerprint2, `${path}.provenance.evaluatorFingerprints.${key}`);
2858
3066
  }
2859
3067
  requireBoolean(provenance.accountingComplete, `${path}.provenance.accountingComplete`);
2860
3068
  const aggregate2 = requireRecord(baseline.aggregate, `${path}.aggregate`);
@@ -2878,6 +3086,9 @@ function validateBaselineShape(value, index) {
2878
3086
  "notEvaluatedTrialCount",
2879
3087
  "unableToEvaluateTrialCount",
2880
3088
  "meanScore",
3089
+ "medianScore",
3090
+ "p95Score",
3091
+ "medianLatencyMs",
2881
3092
  "totalCostUsd",
2882
3093
  "averageCostUsd",
2883
3094
  "scoredTrialCount",
@@ -2889,8 +3100,8 @@ function validateBaselineShape(value, index) {
2889
3100
  for (const [caseIndex, caseValue] of requireArray(baseline.cases, `${path}.cases`).entries()) {
2890
3101
  const casePath = `${path}.cases[${caseIndex}]`;
2891
3102
  const caseAggregate = requireRecord(caseValue, casePath);
2892
- requireString(caseAggregate.caseId, `${casePath}.caseId`);
2893
- requireString(caseAggregate.caseName, `${casePath}.caseName`);
3103
+ requireString2(caseAggregate.caseId, `${casePath}.caseId`);
3104
+ requireString2(caseAggregate.caseName, `${casePath}.caseName`);
2894
3105
  validateNumericRecord(caseAggregate.metrics, `${casePath}.metrics`);
2895
3106
  for (const key of [
2896
3107
  "passRate",
@@ -2944,117 +3155,160 @@ function deserializeEvaluationProjectDataFromString(data) {
2944
3155
  return deserializeEvaluationProjectData(yaml.parse(data));
2945
3156
  }
2946
3157
 
2947
- // src/datasetTransfer.ts
2948
- var DATASET_EXPORT_VERSION = 1;
2949
- function isRecord3(value) {
2950
- return typeof value === "object" && value !== null && !Array.isArray(value);
2951
- }
2952
- function requireString2(value, path) {
2953
- if (typeof value !== "string" || value.length === 0) throw new Error(`${path} must be a non-empty string.`);
2954
- return value;
2955
- }
2956
- function requireText2(value, path) {
2957
- if (typeof value !== "string") throw new Error(`${path} must be a string.`);
2958
- return value;
3158
+ // src/library.ts
3159
+ function createEmptyEvaluationLibrary() {
3160
+ return {
3161
+ version: 1,
3162
+ data: createEmptyEvaluationProjectData(),
3163
+ datasets: [],
3164
+ migratedLegacyProjectIds: []
3165
+ };
2959
3166
  }
2960
- function parseFields(value) {
2961
- if (!Array.isArray(value)) throw new Error("Evaluation dataset fields must be an array.");
2962
- const ids = /* @__PURE__ */ new Set();
2963
- return value.map((field, index) => {
2964
- if (!isRecord3(field)) throw new Error(`fields[${index}] must be an object.`);
2965
- const id = requireString2(field.id, `fields[${index}].id`);
2966
- if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate field id "${id}".`);
2967
- ids.add(id);
2968
- const role = field.role;
2969
- if (role !== "input" && role !== "expected" && role !== "metadata") {
2970
- throw new Error(`fields[${index}].role must be input, expected, or metadata.`);
2971
- }
2972
- if (field.description !== void 0 && typeof field.description !== "string") {
2973
- throw new Error(`fields[${index}].description must be a string.`);
2974
- }
2975
- if (field.required !== void 0 && typeof field.required !== "boolean") {
2976
- throw new Error(`fields[${index}].required must be a boolean.`);
3167
+ function normalizeEvaluationLibrary(value) {
3168
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return createEmptyEvaluationLibrary();
3169
+ const candidate = value;
3170
+ let data = createEmptyEvaluationProjectData();
3171
+ try {
3172
+ data = deserializeEvaluationProjectData(candidate.data);
3173
+ } catch {
3174
+ const dataCandidate = typeof candidate.data === "object" && candidate.data !== null && !Array.isArray(candidate.data) ? candidate.data : void 0;
3175
+ const suites2 = Array.isArray(dataCandidate == null ? void 0 : dataCandidate.suites) ? dataCandidate.suites.flatMap((suite) => {
3176
+ try {
3177
+ return [deserializeEvaluationProjectData({ version: 1, suites: [suite], baselines: [] }).suites[0]];
3178
+ } catch {
3179
+ return [];
3180
+ }
3181
+ }) : [];
3182
+ const baselines = Array.isArray(dataCandidate == null ? void 0 : dataCandidate.baselines) ? dataCandidate.baselines.flatMap((baseline) => {
3183
+ try {
3184
+ return [deserializeEvaluationProjectData({ version: 1, suites: [], baselines: [baseline] }).baselines[0]];
3185
+ } catch {
3186
+ return [];
3187
+ }
3188
+ }) : [];
3189
+ const suiteIds2 = new Set(suites2.map((suite) => suite.id));
3190
+ data = { version: 1, suites: suites2, baselines: baselines.filter((baseline) => suiteIds2.has(baseline.suiteId)) };
3191
+ }
3192
+ const datasets = Array.isArray(candidate.datasets) ? candidate.datasets.flatMap((dataset) => {
3193
+ try {
3194
+ return [localizeEvaluationDataset(dataset)];
3195
+ } catch {
3196
+ return [];
2977
3197
  }
2978
- return {
2979
- id,
2980
- name: requireText2(field.name, `fields[${index}].name`),
2981
- dataType: requireString2(field.dataType, `fields[${index}].dataType`),
2982
- role,
2983
- ...typeof field.description === "string" ? { description: field.description } : {},
2984
- ...typeof field.required === "boolean" ? { required: field.required } : {}
2985
- };
3198
+ }) : [];
3199
+ const suites = uniqueById(data.suites);
3200
+ const suiteIds = new Set(suites.map((suite) => suite.id));
3201
+ return {
3202
+ version: 1,
3203
+ data: {
3204
+ ...data,
3205
+ suites,
3206
+ baselines: uniqueById(data.baselines).filter((baseline) => suiteIds.has(baseline.suiteId))
3207
+ },
3208
+ datasets: uniqueById(datasets),
3209
+ migratedLegacyProjectIds: Array.isArray(candidate.migratedLegacyProjectIds) ? Array.from(new Set(candidate.migratedLegacyProjectIds.filter((id) => typeof id === "string"))) : []
3210
+ };
3211
+ }
3212
+ function uniqueById(values) {
3213
+ const seen = /* @__PURE__ */ new Set();
3214
+ return values.filter((value) => {
3215
+ if (seen.has(value.id)) return false;
3216
+ seen.add(value.id);
3217
+ return true;
2986
3218
  });
2987
3219
  }
2988
- function parseCases(value, fieldIds) {
2989
- if (!Array.isArray(value)) throw new Error("Evaluation dataset cases must be an array.");
2990
- const ids = /* @__PURE__ */ new Set();
2991
- return value.map((testCase, index) => {
2992
- if (!isRecord3(testCase)) throw new Error(`cases[${index}] must be an object.`);
2993
- const id = requireString2(testCase.id, `cases[${index}].id`);
2994
- if (ids.has(id)) throw new Error(`Evaluation dataset has duplicate case id "${id}".`);
2995
- ids.add(id);
2996
- if (!isRecord3(testCase.values)) throw new Error(`cases[${index}].values must be an object.`);
2997
- for (const [fieldId, fieldValue] of Object.entries(testCase.values)) {
2998
- if (!fieldIds.has(fieldId)) throw new Error(`cases[${index}].values references unknown field "${fieldId}".`);
2999
- assertPortableJson(fieldValue, `cases[${index}].values.${fieldId}`);
3220
+
3221
+ // src/store.ts
3222
+ var InMemoryEvaluationRunStore = class {
3223
+ #runs = /* @__PURE__ */ new Map();
3224
+ #datasetSnapshots = /* @__PURE__ */ new Map();
3225
+ #recordings = /* @__PURE__ */ new Map();
3226
+ #library = createEmptyEvaluationLibrary();
3227
+ async getLibrary() {
3228
+ return structuredClone(this.#library);
3229
+ }
3230
+ async putLibrary(library) {
3231
+ this.#library = structuredClone(normalizeEvaluationLibrary(library));
3232
+ }
3233
+ async put(run) {
3234
+ const normalized = normalizeEvaluationRun(run);
3235
+ const key = `${normalized.projectId}/${normalized.id}`;
3236
+ const existing = this.#runs.get(key);
3237
+ const next = reconcileEvaluationRunSnapshots(existing, normalized);
3238
+ if (next === existing) return;
3239
+ this.#runs.set(key, structuredClone(next));
3240
+ }
3241
+ async updateRunName(input) {
3242
+ const key = `${input.projectId}/${input.runId}`;
3243
+ const existing = this.#runs.get(key);
3244
+ if (!existing) return void 0;
3245
+ const renamed = normalizeEvaluationRun({ ...existing, name: input.name });
3246
+ this.#runs.set(key, structuredClone(renamed));
3247
+ return structuredClone(renamed);
3248
+ }
3249
+ async get(input) {
3250
+ const run = this.#runs.get(`${input.projectId}/${input.runId}`);
3251
+ return run ? normalizeEvaluationRun(run) : void 0;
3252
+ }
3253
+ async list(input) {
3254
+ return Array.from(this.#runs.values()).filter(
3255
+ (run) => run.projectId === input.projectId && (input.suiteId === void 0 || run.suiteId === input.suiteId)
3256
+ ).sort((left, right) => right.startedAt.localeCompare(left.startedAt)).map(normalizeEvaluationRun);
3257
+ }
3258
+ async delete(input) {
3259
+ this.#runs.delete(`${input.projectId}/${input.runId}`);
3260
+ for (const [key, artifact] of this.#recordings) {
3261
+ if (artifact.projectId === input.projectId && artifact.runId === input.runId) this.#recordings.delete(key);
3000
3262
  }
3001
- if (testCase.tags !== void 0 && (!Array.isArray(testCase.tags) || testCase.tags.some((tag) => typeof tag !== "string"))) {
3002
- throw new Error(`cases[${index}].tags must be an array of strings.`);
3263
+ }
3264
+ async putDatasetSnapshot(snapshot) {
3265
+ assertEvaluationDatasetSnapshot(snapshot);
3266
+ const key = `${snapshot.projectId}/${snapshot.fingerprint}`;
3267
+ if (this.#datasetSnapshots.has(key)) return;
3268
+ this.#datasetSnapshots.set(key, structuredClone(snapshot));
3269
+ }
3270
+ async getDatasetSnapshot(input) {
3271
+ const snapshot = this.#datasetSnapshots.get(`${input.projectId}/${input.fingerprint}`);
3272
+ return snapshot ? structuredClone(snapshot) : void 0;
3273
+ }
3274
+ async putRecording(artifact) {
3275
+ assertEvaluationRecordingArtifact(artifact);
3276
+ const key = `${artifact.projectId}/${artifact.reference.id}`;
3277
+ const existing = this.#recordings.get(key);
3278
+ if (existing && (existing.runId !== artifact.runId || existing.trialId !== artifact.trialId)) {
3279
+ throw new Error("An evaluation recording ID cannot be reassigned to another run or trial.");
3003
3280
  }
3004
- if (testCase.note !== void 0 && typeof testCase.note !== "string") throw new Error(`cases[${index}].note must be a string.`);
3005
- if (testCase.enabled !== void 0 && typeof testCase.enabled !== "boolean") throw new Error(`cases[${index}].enabled must be a boolean.`);
3006
- return {
3007
- id,
3008
- name: requireText2(testCase.name, `cases[${index}].name`),
3009
- values: testCase.values,
3010
- ...typeof testCase.enabled === "boolean" ? { enabled: testCase.enabled } : {},
3011
- ...Array.isArray(testCase.tags) ? { tags: testCase.tags } : {},
3012
- ...typeof testCase.note === "string" ? { note: testCase.note } : {}
3281
+ const next = structuredClone(artifact);
3282
+ if (existing) next.reference = structuredClone(existing.reference);
3283
+ this.#recordings.set(key, next);
3284
+ }
3285
+ async getRecording(input) {
3286
+ const artifact = this.#recordings.get(`${input.projectId}/${input.recordingId}`);
3287
+ if (!artifact) return void 0;
3288
+ if (artifact.reference.retention === "temporary" && artifact.reference.expiresAt && Date.parse(artifact.reference.expiresAt) <= Date.now()) {
3289
+ this.#recordings.delete(`${input.projectId}/${input.recordingId}`);
3290
+ return void 0;
3291
+ }
3292
+ return structuredClone(artifact);
3293
+ }
3294
+ async updateRecordingRetention(input) {
3295
+ const key = `${input.projectId}/${input.recordingId}`;
3296
+ const artifact = this.#recordings.get(key);
3297
+ if (!artifact) return false;
3298
+ artifact.reference = {
3299
+ id: artifact.reference.id,
3300
+ retention: input.retention,
3301
+ ...input.expiresAt === void 0 ? {} : { expiresAt: input.expiresAt }
3013
3302
  };
3014
- });
3015
- }
3016
- function validateEvaluationDataset(value, scope) {
3017
- if (!isRecord3(value)) throw new Error("Evaluation dataset must be an object.");
3018
- const fields = parseFields(value.fields);
3019
- const importedProjectId = value.projectId === void 0 ? void 0 : requireString2(value.projectId, "dataset.projectId");
3020
- const projectId = (scope == null ? void 0 : scope.projectId) ?? importedProjectId;
3021
- const dataset = {
3022
- id: (scope == null ? void 0 : scope.id) ?? requireString2(value.id, "dataset.id"),
3023
- name: requireText2(value.name, "dataset.name"),
3024
- ...projectId === void 0 ? {} : { projectId },
3025
- fields,
3026
- cases: parseCases(value.cases, new Set(fields.map((field) => field.id)))
3027
- };
3028
- return { ...dataset, contentFingerprint: fingerprintEvaluationDataset(dataset) };
3029
- }
3030
- function localizeEvaluationDataset(value, id) {
3031
- const dataset = validateEvaluationDataset(value, id === void 0 ? void 0 : { id });
3032
- const { projectId: _legacyProjectId, ...localDataset } = dataset;
3033
- return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
3034
- }
3035
- function validateEvaluationDatasetForTransfer(value, scope) {
3036
- const dataset = validateEvaluationDataset(value, scope);
3037
- assertEvaluationDatasetValuesMatchDeclaredTypes(dataset);
3038
- return dataset;
3039
- }
3040
- function serializeEvaluationDatasetJson(dataset) {
3041
- const validated = validateEvaluationDatasetForTransfer(dataset);
3042
- return JSON.stringify({ version: DATASET_EXPORT_VERSION, dataset: validated }, null, 2);
3043
- }
3044
- function deserializeEvaluationDatasetJson(source, scope) {
3045
- let parsed;
3046
- try {
3047
- parsed = JSON.parse(source);
3048
- } catch {
3049
- throw new Error("Evaluation dataset JSON is not valid JSON.");
3303
+ return true;
3050
3304
  }
3051
- if (!isRecord3(parsed) || parsed.version !== DATASET_EXPORT_VERSION || !isRecord3(parsed.dataset)) {
3052
- throw new Error(`Expected an evaluation dataset export with version ${DATASET_EXPORT_VERSION}.`);
3305
+ async promoteBaseline(input) {
3306
+ for (const artifact of this.#recordings.values()) {
3307
+ if (artifact.projectId !== input.projectId || artifact.runId !== input.runId) continue;
3308
+ artifact.reference = { id: artifact.reference.id, retention: "baseline" };
3309
+ }
3053
3310
  }
3054
- const dataset = validateEvaluationDatasetForTransfer(parsed.dataset, { id: scope.id });
3055
- const { projectId: _legacyProjectId, ...localDataset } = dataset;
3056
- return { ...localDataset, contentFingerprint: fingerprintEvaluationDataset(localDataset) };
3057
- }
3311
+ };
3058
3312
 
3059
3313
  // src/suiteTransfer.ts
3060
3314
  var SUITE_BUNDLE_EXPORT_VERSION = 1;
@@ -3114,13 +3368,17 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
3114
3368
  areEvaluationDataTypesCompatible,
3115
3369
  assertEvaluationDatasetSnapshot,
3116
3370
  assertEvaluationDatasetValuesMatchDeclaredTypes,
3371
+ assertEvaluationRecordingArtifact,
3117
3372
  assertPortableJson,
3118
3373
  canonicalStringify,
3119
3374
  canonicalizePortableJson,
3120
3375
  clonePortableJson,
3376
+ createEmptyEvaluationLibrary,
3121
3377
  createEmptyEvaluationProjectData,
3122
3378
  createEmptyMetrics,
3123
3379
  createEvaluationBaselineSnapshot,
3380
+ createEvaluationRunPlan,
3381
+ createEvaluationRunShell,
3124
3382
  deserializeEvaluationDatasetJson,
3125
3383
  deserializeEvaluationProjectData,
3126
3384
  deserializeEvaluationProjectDataFromString,
@@ -3128,6 +3386,7 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
3128
3386
  evaluateAssertion,
3129
3387
  evaluateThresholdResults,
3130
3388
  finalizeEvaluationRecordingRetention,
3389
+ finalizeEvaluationRun,
3131
3390
  fingerprint,
3132
3391
  fingerprintEvaluationDataset,
3133
3392
  getEvaluationSuiteMode,
@@ -3139,11 +3398,15 @@ function deserializeEvaluationSuiteBundleJson(source, scope) {
3139
3398
  localizeEvaluationDataset,
3140
3399
  normalizeEvaluationAggregate,
3141
3400
  normalizeEvaluationBaselineSnapshot,
3401
+ normalizeEvaluationLibrary,
3142
3402
  normalizeEvaluationRun,
3143
3403
  normalizeEvaluationTrial,
3144
3404
  portableEvaluationDataTypes,
3405
+ preserveEvaluationRunName,
3406
+ reconcileEvaluationRunSnapshots,
3145
3407
  runEvaluationCases,
3146
3408
  runEvaluationSuite,
3409
+ runEvaluationTrial,
3147
3410
  runEvaluationWorkPool,
3148
3411
  serializeEvaluationDatasetJson,
3149
3412
  serializeEvaluationProjectData,