@tangle-network/agent-eval 0.126.7 → 0.128.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.
Files changed (132) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +6 -2
  3. package/dist/analyst/index.d.ts +52 -43
  4. package/dist/analyst/index.js +4 -4
  5. package/dist/belief-state/index.d.ts +35 -40
  6. package/dist/belief-state/index.js +2 -2
  7. package/dist/benchmarks/index.d.ts +30 -12
  8. package/dist/benchmarks/index.js +12 -11
  9. package/dist/builder-eval/index.js +1 -1
  10. package/dist/campaign/index.d.ts +441 -160
  11. package/dist/campaign/index.js +11 -10
  12. package/dist/{chunk-IILEIWGW.js → chunk-2JX3CFMB.js} +100 -40
  13. package/dist/chunk-2JX3CFMB.js.map +1 -0
  14. package/dist/chunk-2MKQIFS4.js +183 -0
  15. package/dist/chunk-2MKQIFS4.js.map +1 -0
  16. package/dist/{chunk-NUKSVU3W.js → chunk-5DTSBUL2.js} +2 -2
  17. package/dist/{chunk-H5UD2323.js → chunk-7ZZMD7UK.js} +102 -2
  18. package/dist/chunk-7ZZMD7UK.js.map +1 -0
  19. package/dist/{chunk-474LBSOX.js → chunk-BYT7ELPS.js} +45 -18
  20. package/dist/chunk-BYT7ELPS.js.map +1 -0
  21. package/dist/{chunk-LUNF2SEL.js → chunk-DJKY2TSY.js} +3 -3
  22. package/dist/chunk-DPUHNQLN.js +232 -0
  23. package/dist/chunk-DPUHNQLN.js.map +1 -0
  24. package/dist/chunk-DRYIUNWY.js +622 -0
  25. package/dist/chunk-DRYIUNWY.js.map +1 -0
  26. package/dist/{chunk-IPYXE555.js → chunk-EJGRPCO3.js} +36 -13
  27. package/dist/chunk-EJGRPCO3.js.map +1 -0
  28. package/dist/{chunk-OCFJACJU.js → chunk-EOSZT7PL.js} +82 -18
  29. package/dist/chunk-EOSZT7PL.js.map +1 -0
  30. package/dist/{chunk-ZVCHKKOP.js → chunk-EZJEIH2R.js} +39 -15
  31. package/dist/chunk-EZJEIH2R.js.map +1 -0
  32. package/dist/{chunk-M7AH34KV.js → chunk-IHQDPH7D.js} +32 -16
  33. package/dist/chunk-IHQDPH7D.js.map +1 -0
  34. package/dist/{chunk-PJQFMIOX.js → chunk-MHELPNRP.js} +31 -1
  35. package/dist/chunk-MHELPNRP.js.map +1 -0
  36. package/dist/{chunk-K6IAZZ6L.js → chunk-NACAGYSY.js} +153 -122
  37. package/dist/chunk-NACAGYSY.js.map +1 -0
  38. package/dist/{chunk-BTACVO2W.js → chunk-NKAGIDE2.js} +542 -258
  39. package/dist/chunk-NKAGIDE2.js.map +1 -0
  40. package/dist/{chunk-ARU2PZFM.js → chunk-NYLOYM6N.js} +43 -23
  41. package/dist/chunk-NYLOYM6N.js.map +1 -0
  42. package/dist/{chunk-QOTFXW5L.js → chunk-P5W7RQKK.js} +2 -2
  43. package/dist/{chunk-IR3KBHOY.js → chunk-P6FYH6K4.js} +39 -3
  44. package/dist/chunk-P6FYH6K4.js.map +1 -0
  45. package/dist/{chunk-J5SQWP6Y.js → chunk-PBE2LOSS.js} +18 -2
  46. package/dist/chunk-PBE2LOSS.js.map +1 -0
  47. package/dist/{chunk-X4UCIOTZ.js → chunk-S5YLIBFX.js} +2 -2
  48. package/dist/{chunk-7VYEGHWF.js → chunk-SZLVEKMJ.js} +41 -20
  49. package/dist/chunk-SZLVEKMJ.js.map +1 -0
  50. package/dist/{chunk-P22LJ3Y2.js → chunk-TBL77AUT.js} +8 -8
  51. package/dist/chunk-TBL77AUT.js.map +1 -0
  52. package/dist/{chunk-LKKT3IVV.js → chunk-TSN7JT6D.js} +6 -4
  53. package/dist/{chunk-LKKT3IVV.js.map → chunk-TSN7JT6D.js.map} +1 -1
  54. package/dist/{chunk-E3IADPLG.js → chunk-UB2LOJ6Q.js} +51 -227
  55. package/dist/chunk-UB2LOJ6Q.js.map +1 -0
  56. package/dist/{chunk-MAX3TN3C.js → chunk-UWZZKKU7.js} +7 -19
  57. package/dist/chunk-UWZZKKU7.js.map +1 -0
  58. package/dist/{chunk-DPZAEKA6.js → chunk-VLOATJQ2.js} +85 -57
  59. package/dist/chunk-VLOATJQ2.js.map +1 -0
  60. package/dist/{chunk-MOXWMGPC.js → chunk-XDWDC2MP.js} +197 -79
  61. package/dist/chunk-XDWDC2MP.js.map +1 -0
  62. package/dist/{chunk-CGG5SLH3.js → chunk-XPRT64IE.js} +2 -2
  63. package/dist/{chunk-FO7HEH76.js → chunk-YJBNWCAA.js} +2 -2
  64. package/dist/{chunk-KE2VWPZX.js → chunk-ZUUWPZCV.js} +3 -3
  65. package/dist/cli.js +4 -4
  66. package/dist/contract/index.d.ts +438 -138
  67. package/dist/contract/index.js +259 -113
  68. package/dist/contract/index.js.map +1 -1
  69. package/dist/control.d.ts +56 -39
  70. package/dist/control.js +2 -3
  71. package/dist/hosted/index.d.ts +152 -51
  72. package/dist/hosted/index.js +24 -1
  73. package/dist/index.d.ts +399 -277
  74. package/dist/index.js +363 -244
  75. package/dist/index.js.map +1 -1
  76. package/dist/meta-eval/index.d.ts +33 -36
  77. package/dist/meta-eval/index.js +2 -2
  78. package/dist/multishot/index.d.ts +1 -0
  79. package/dist/openapi.json +1 -1
  80. package/dist/pipelines/index.js +2 -2
  81. package/dist/reporting.d.ts +101 -80
  82. package/dist/reporting.js +7 -5
  83. package/dist/rl.d.ts +198 -142
  84. package/dist/rl.js +235 -127
  85. package/dist/rl.js.map +1 -1
  86. package/dist/rollout/index.d.ts +54 -67
  87. package/dist/rollout/index.js +6 -5
  88. package/dist/{run-campaign-FFRM3RH5.js → run-campaign-ISHFZ7FJ.js} +6 -3
  89. package/dist/supervisor-run/index.d.ts +7 -15
  90. package/dist/supervisor-run/index.js +2 -2
  91. package/dist/traces.d.ts +72 -50
  92. package/dist/traces.js +9 -6
  93. package/dist/wire/index.d.ts +1 -0
  94. package/dist/wire/index.js +2 -2
  95. package/docs/concepts.md +18 -1
  96. package/docs/customer-journeys.md +7 -6
  97. package/docs/hosted-ingest-spec.md +37 -40
  98. package/docs/insight-report.md +78 -7
  99. package/docs/research-report-methodology.md +20 -21
  100. package/docs/rollout.md +13 -8
  101. package/package.json +4 -3
  102. package/dist/chunk-474LBSOX.js.map +0 -1
  103. package/dist/chunk-7VYEGHWF.js.map +0 -1
  104. package/dist/chunk-ARU2PZFM.js.map +0 -1
  105. package/dist/chunk-BTACVO2W.js.map +0 -1
  106. package/dist/chunk-DPZAEKA6.js.map +0 -1
  107. package/dist/chunk-E3IADPLG.js.map +0 -1
  108. package/dist/chunk-H5UD2323.js.map +0 -1
  109. package/dist/chunk-IILEIWGW.js.map +0 -1
  110. package/dist/chunk-IPYXE555.js.map +0 -1
  111. package/dist/chunk-IR3KBHOY.js.map +0 -1
  112. package/dist/chunk-J5SQWP6Y.js.map +0 -1
  113. package/dist/chunk-K6IAZZ6L.js.map +0 -1
  114. package/dist/chunk-M7AH34KV.js.map +0 -1
  115. package/dist/chunk-MAX3TN3C.js.map +0 -1
  116. package/dist/chunk-MOXWMGPC.js.map +0 -1
  117. package/dist/chunk-OCFJACJU.js.map +0 -1
  118. package/dist/chunk-P22LJ3Y2.js.map +0 -1
  119. package/dist/chunk-PJQFMIOX.js.map +0 -1
  120. package/dist/chunk-VSMTAMNK.js +0 -53
  121. package/dist/chunk-VSMTAMNK.js.map +0 -1
  122. package/dist/chunk-ZVCHKKOP.js.map +0 -1
  123. package/dist/chunk-ZZUXHH3R.js +0 -99
  124. package/dist/chunk-ZZUXHH3R.js.map +0 -1
  125. /package/dist/{chunk-NUKSVU3W.js.map → chunk-5DTSBUL2.js.map} +0 -0
  126. /package/dist/{chunk-LUNF2SEL.js.map → chunk-DJKY2TSY.js.map} +0 -0
  127. /package/dist/{chunk-QOTFXW5L.js.map → chunk-P5W7RQKK.js.map} +0 -0
  128. /package/dist/{chunk-X4UCIOTZ.js.map → chunk-S5YLIBFX.js.map} +0 -0
  129. /package/dist/{chunk-CGG5SLH3.js.map → chunk-XPRT64IE.js.map} +0 -0
  130. /package/dist/{chunk-FO7HEH76.js.map → chunk-YJBNWCAA.js.map} +0 -0
  131. /package/dist/{chunk-KE2VWPZX.js.map → chunk-ZUUWPZCV.js.map} +0 -0
  132. /package/dist/{run-campaign-FFRM3RH5.js.map → run-campaign-ISHFZ7FJ.js.map} +0 -0
@@ -1,25 +1,25 @@
1
- import {
2
- summarizeBackendIntegrity
3
- } from "./chunk-ZVCHKKOP.js";
4
1
  import {
5
2
  paretoChart
6
- } from "./chunk-DPZAEKA6.js";
3
+ } from "./chunk-VLOATJQ2.js";
4
+ import {
5
+ pairRunRecords
6
+ } from "./chunk-DPUHNQLN.js";
7
7
  import {
8
- cohensD,
9
8
  continuousAgreement,
10
9
  pairedBootstrap,
10
+ pairedCohensDz,
11
11
  pairedMde,
12
12
  pairedTTest,
13
13
  pearsonR,
14
- requiredSampleSize,
14
+ requiredPairedSampleSize,
15
15
  spearmanR
16
- } from "./chunk-PJQFMIOX.js";
16
+ } from "./chunk-MHELPNRP.js";
17
17
  import {
18
18
  llmSpans
19
19
  } from "./chunk-ZET2UAYW.js";
20
20
  import {
21
- resolveRunCostProvenance
22
- } from "./chunk-IILEIWGW.js";
21
+ validateRunRecord
22
+ } from "./chunk-2JX3CFMB.js";
23
23
 
24
24
  // src/contamination-guard.ts
25
25
  function checkCanaries(output, scenarios) {
@@ -124,14 +124,15 @@ function excerpt(source, needle) {
124
124
 
125
125
  // src/contract/analyze-runs.ts
126
126
  function summarizeExecution(opts) {
127
+ const runs = opts.runs.map(validateRunRecord);
127
128
  const bins = opts.histogramBins ?? 12;
128
129
  return {
129
- execution: computeExecutionInsight(opts.runs, bins),
130
- costProvenance: summarizeCostProvenance(opts.runs)
130
+ execution: computeExecutionInsight(runs, bins),
131
+ costProvenance: summarizeCostProvenance(runs)
131
132
  };
132
133
  }
133
134
  async function analyzeRuns(opts) {
134
- const runs = opts.runs;
135
+ const runs = opts.runs.map(validateRunRecord);
135
136
  const bins = opts.histogramBins ?? 12;
136
137
  const threshold = opts.decisionThreshold ?? 0.02;
137
138
  const split = resolveSplit(runs, opts.split ?? "auto");
@@ -146,15 +147,15 @@ async function analyzeRuns(opts) {
146
147
  runs,
147
148
  histogramBins: bins
148
149
  });
149
- const knownCostRuns = runs.filter((run) => resolveRunCostProvenance(run).kind !== "uncaptured");
150
- const costs = knownCostRuns.map((r) => r.costUsd).filter(Number.isFinite);
150
+ const knownCostRuns = runs.filter((run) => run.costProvenance.kind !== "uncaptured");
151
+ const costs = knownCostRuns.map((r) => r.costUsd).filter(isFiniteNumber);
151
152
  const costDist = distributionOf(costs, bins);
152
153
  const pareto = paretoChart(knownCostRuns, { split });
153
154
  const degraded = {};
154
155
  if (provenance.uncaptured.n > 0) {
155
156
  degraded.cost = diagnoseCostCoverage(runs, provenance);
156
157
  } else if (costs.length === 0 || costs.every((c) => c === 0)) {
157
- degraded.cost = runs.length > 0 && runs.every((run) => run.costProvenance !== void 0) ? `all ${runs.length} explicitly observed or estimated USD values are $0` : diagnoseZeroCost(runs);
158
+ degraded.cost = `all ${runs.length} explicitly observed or estimated USD values are $0`;
158
159
  }
159
160
  if (pareto.points.length < 2) {
160
161
  degraded.pareto = pareto.points.length === 0 ? "no candidates \u2014 Pareto unavailable" : "single candidate \u2014 Pareto is a single point, not a frontier";
@@ -169,7 +170,7 @@ async function analyzeRuns(opts) {
169
170
  const interRater = opts.raterScores ? computeInterRater(opts.raterScores) : void 0;
170
171
  const lift = computeLift(runs, opts.baselineCandidateId, opts.candidateCandidateId, split);
171
172
  const failureClusters = opts.analyst ? await computeFailureClusters(runs, opts.analyst, split) : void 0;
172
- const failureModes = computeFailureModes(runs);
173
+ const failureClasses = computeFailureClasses(runs, split);
173
174
  const contamination = opts.canaryScenarios ? computeContamination(runs, opts.canaryScenarios) : void 0;
174
175
  const outcomeCorrelation = opts.outcomeSignal ? computeOutcomeCorrelation(runs, opts.outcomeSignal, split) : void 0;
175
176
  const release = buildReleaseScorecard(composite, lift, contamination);
@@ -180,7 +181,7 @@ async function analyzeRuns(opts) {
180
181
  interRater,
181
182
  lift,
182
183
  failureClusters,
183
- failureModes,
184
+ failureClasses,
184
185
  contamination,
185
186
  outcomeCorrelation,
186
187
  priorPeriodComparison,
@@ -199,7 +200,7 @@ async function analyzeRuns(opts) {
199
200
  contamination,
200
201
  outcomeCorrelation,
201
202
  release,
202
- ...failureModes ? { failureModes } : {},
203
+ ...failureClasses ? { failureClasses } : {},
203
204
  ...priorPeriodComparison ? { priorPeriodComparison } : {},
204
205
  recommendations
205
206
  };
@@ -213,16 +214,34 @@ function computeExecutionInsight(runs, bins) {
213
214
  (row) => row.costUsd !== void 0 ? [row.costUsd] : []
214
215
  );
215
216
  const modelCounts = /* @__PURE__ */ new Map();
216
- let failureRuns = 0;
217
- let reportedErrorEvents = 0;
217
+ let executionErrorRuns = 0;
218
+ let executionErrorEvents = 0;
218
219
  let errorReportingRuns = 0;
220
+ let errorSpanEvents = 0;
221
+ let errorSpanReportingRuns = 0;
222
+ const terminalOutcomes = {
223
+ succeeded: 0,
224
+ failed: 0,
225
+ cancelled: 0,
226
+ incomplete: 0,
227
+ unknown: 0
228
+ };
229
+ const errorsByTerminalOutcome = {
230
+ succeeded: { withErrors: 0, withoutErrors: 0, unreported: 0 },
231
+ failed: { withErrors: 0, withoutErrors: 0, unreported: 0 },
232
+ cancelled: { withErrors: 0, withoutErrors: 0, unreported: 0 },
233
+ incomplete: { withErrors: 0, withoutErrors: 0, unreported: 0 },
234
+ unknown: { withErrors: 0, withoutErrors: 0, unreported: 0 }
235
+ };
219
236
  let modelCallRuns = 0;
220
237
  let modelCallEvents = 0;
221
238
  let modelCallReportingRuns = 0;
222
239
  for (const run of runs) {
223
240
  modelCounts.set(run.model, (modelCounts.get(run.model) ?? 0) + 1);
224
- const modelCalls = run.outcome.raw.llm_span_count;
225
- if (Number.isFinite(modelCalls)) {
241
+ const terminalOutcome = run.terminalOutcome;
242
+ terminalOutcomes[terminalOutcome] += 1;
243
+ const modelCalls = nonNegativeCountRaw(run, "llm_span_count");
244
+ if (modelCalls !== void 0) {
226
245
  modelCallEvents += modelCalls;
227
246
  modelCallReportingRuns += 1;
228
247
  }
@@ -230,13 +249,19 @@ function computeExecutionInsight(runs, bins) {
230
249
  if ((modelCalls ?? 0) > 0 || usage.input > 0 || usage.output > 0 || (usage.cached ?? 0) > 0 || (usage.cacheWrite ?? 0) > 0) {
231
250
  modelCallRuns += 1;
232
251
  }
233
- const errorEvents = run.outcome.raw.error_span_count;
234
- if (Number.isFinite(errorEvents)) {
235
- reportedErrorEvents += errorEvents;
252
+ const errorEvents = reportedExecutionErrorEvents(run);
253
+ if (errorEvents !== void 0) {
254
+ executionErrorEvents += errorEvents;
236
255
  errorReportingRuns += 1;
237
- }
238
- if (run.failureClass !== void 0 && run.failureClass !== "success" || run.failureMode !== void 0 || (errorEvents ?? 0) > 0) {
239
- failureRuns += 1;
256
+ if (errorEvents > 0) {
257
+ executionErrorRuns += 1;
258
+ errorsByTerminalOutcome[terminalOutcome].withErrors += 1;
259
+ } else errorsByTerminalOutcome[terminalOutcome].withoutErrors += 1;
260
+ } else errorsByTerminalOutcome[terminalOutcome].unreported += 1;
261
+ const reportedErrorSpans = nonNegativeCountRaw(run, "error_span_count");
262
+ if (reportedErrorSpans !== void 0) {
263
+ errorSpanEvents += reportedErrorSpans;
264
+ errorSpanReportingRuns += 1;
240
265
  }
241
266
  }
242
267
  return {
@@ -267,14 +292,28 @@ function computeExecutionInsight(runs, bins) {
267
292
  events: modelCallEvents,
268
293
  reportingRuns: modelCallReportingRuns
269
294
  },
270
- failures: {
271
- runs: failureRuns,
272
- fraction: runs.length > 0 ? failureRuns / runs.length : 0,
273
- reportedErrorEvents,
274
- reportingRuns: errorReportingRuns
275
- }
295
+ executionErrors: {
296
+ runs: executionErrorRuns,
297
+ fraction: errorReportingRuns > 0 ? executionErrorRuns / errorReportingRuns : null,
298
+ events: executionErrorEvents,
299
+ reportingRuns: errorReportingRuns,
300
+ errorSpanEvents,
301
+ errorSpanReportingRuns,
302
+ byTerminalOutcome: errorsByTerminalOutcome
303
+ },
304
+ terminalOutcomes
276
305
  };
277
306
  }
307
+ function reportedExecutionErrorEvents(run) {
308
+ const canonical = nonNegativeCountRaw(run, "execution_error_count");
309
+ if (canonical !== void 0) return canonical;
310
+ const toolErrors = nonNegativeCountRaw(run, "tool_errors");
311
+ return toolErrors;
312
+ }
313
+ function nonNegativeCountRaw(run, key) {
314
+ const value = finiteRaw(run, key);
315
+ return value !== void 0 && Number.isInteger(value) && value >= 0 ? value : void 0;
316
+ }
278
317
  function summarizeTokenUsage(usages, bins) {
279
318
  const reasoning = usages.flatMap(
280
319
  (usage) => usage.reasoning !== void 0 ? [usage.reasoning] : []
@@ -332,7 +371,7 @@ function summarizeCostProvenance(runs) {
332
371
  knownFraction: 0
333
372
  };
334
373
  for (const run of runs) {
335
- const cost = resolveRunCostProvenance(run);
374
+ const cost = run.costProvenance;
336
375
  if (cost.kind === "uncaptured") {
337
376
  summary.uncaptured.n += 1;
338
377
  } else {
@@ -347,36 +386,25 @@ function summarizeCostProvenance(runs) {
347
386
  function diagnoseCostCoverage(runs, provenance) {
348
387
  const uncaptured = provenance.uncaptured.n;
349
388
  const known = provenance.observed.n + provenance.estimated.n;
350
- const explicitUncaptured = runs.some((run) => run.costProvenance?.kind === "uncaptured");
351
- if (uncaptured === runs.length && !explicitUncaptured) return diagnoseZeroCost(runs);
352
389
  if (uncaptured === runs.length) {
353
390
  return `USD cost uncaptured for all ${runs.length} runs \u2014 no observed or estimated USD values; token and wall-time metrics remain available.`;
354
391
  }
355
392
  return `USD cost uncaptured for ${uncaptured}/${runs.length} runs; excluded those rows from cost statistics (${known}/${runs.length} retained: ${provenance.observed.n} observed, ${provenance.estimated.n} estimated).`;
356
393
  }
357
- function diagnoseZeroCost(runs) {
358
- const integrity = summarizeBackendIntegrity(runs);
359
- const { totalRecords, stubRecords, uncostedRecords } = integrity;
360
- if (totalRecords > 0 && stubRecords === totalRecords) {
361
- return `no costUsd values recorded \u2014 all ${totalRecords} records are stub-mode (zero token usage). The backend never reported real LLM activity, so cost cannot be computed; verify the backend actually ran before trusting this corpus.`;
362
- }
363
- if (uncostedRecords > 0) {
364
- return `no costUsd values recorded \u2014 ${uncostedRecords}/${totalRecords} records have token usage but $0 cost (unpriced model). Check isModelPriced(model) for the run's model id and add it to FAMILY_PRICING.`;
365
- }
366
- if (stubRecords > 0) {
367
- return `no costUsd values recorded \u2014 ${stubRecords}/${totalRecords} records are stub-mode (zero token usage); the remainder reported neither tokens nor cost. Cost axis carries no signal.`;
368
- }
369
- return "no costUsd values recorded \u2014 cost axis carries no signal";
370
- }
371
- function computeFailureModes(runs) {
394
+ function computeFailureClasses(runs, split) {
372
395
  const counts = /* @__PURE__ */ new Map();
373
396
  for (const r of runs) {
374
- const key = r.failureClass ?? r.failureMode;
375
- if (key) counts.set(key, (counts.get(key) ?? 0) + 1);
397
+ if (!isTaskFailure(r, split)) continue;
398
+ const key = r.failureClass !== void 0 && r.failureClass !== "success" ? r.failureClass : "unknown";
399
+ counts.set(key, (counts.get(key) ?? 0) + 1);
376
400
  }
377
401
  if (counts.size === 0) return void 0;
378
402
  const n = runs.length;
379
- return [...counts.entries()].map(([mode, count]) => ({ mode, count, share: n > 0 ? count / n : 0 })).sort((a, b) => b.count - a.count || a.mode.localeCompare(b.mode));
403
+ return [...counts.entries()].map(([failureClass, count]) => ({
404
+ failureClass,
405
+ count,
406
+ share: n > 0 ? count / n : 0
407
+ })).sort((a, b) => b.count - a.count || a.failureClass.localeCompare(b.failureClass));
380
408
  }
381
409
  function computePriorPeriodComparison(current, baseline, split, windowLabel) {
382
410
  if (current.length === 0 || baseline.length === 0) return void 0;
@@ -434,7 +462,10 @@ function computePriorPeriodComparison(current, baseline, split, windowLabel) {
434
462
  };
435
463
  }
436
464
  function knownCostValues(runs) {
437
- return runs.filter((run) => resolveRunCostProvenance(run).kind !== "uncaptured").map((run) => run.costUsd).filter(Number.isFinite);
465
+ return runs.filter((run) => run.costProvenance.kind !== "uncaptured").map((run) => run.costUsd).filter(isFiniteNumber);
466
+ }
467
+ function isFiniteNumber(value) {
468
+ return typeof value === "number" && Number.isFinite(value);
438
469
  }
439
470
  function collectPerDimension(runs) {
440
471
  const out = {};
@@ -465,15 +496,15 @@ function welchCompare(baseline, current) {
465
496
  const pooledStddev = Math.sqrt(
466
497
  ((baselineN - 1) * baselineVar + (currentN - 1) * currentVar) / Math.max(1, baselineN + currentN - 2)
467
498
  );
468
- const cohensD2 = pooledStddev > 0 ? delta / pooledStddev : 0;
469
- const significant = pValue < 0.05 && Math.abs(cohensD2) >= 0.2;
499
+ const cohensD = pooledStddev > 0 ? delta / pooledStddev : 0;
500
+ const significant = pValue < 0.05 && Math.abs(cohensD) >= 0.2;
470
501
  return {
471
502
  current: currentMean,
472
503
  baseline: baselineMean,
473
504
  delta,
474
505
  ci95,
475
506
  pValue,
476
- cohensD: cohensD2,
507
+ cohensD,
477
508
  baselineN,
478
509
  currentN,
479
510
  significant
@@ -504,21 +535,19 @@ function resolveSplit(runs, pref) {
504
535
  return hasHoldout ? "holdout" : "search";
505
536
  }
506
537
  function compositeOf(run, split) {
507
- const primary = split === "holdout" ? run.outcome.holdoutScore : run.outcome.searchScore;
508
- if (Number.isFinite(primary)) return primary;
509
- const alt = split === "holdout" ? run.outcome.searchScore : run.outcome.holdoutScore;
510
- return Number.isFinite(alt) ? alt : Number.NaN;
538
+ const score = split === "holdout" ? run.outcome.holdoutScore : run.outcome.searchScore;
539
+ return Number.isFinite(score) ? score : Number.NaN;
511
540
  }
512
541
  function distributionOf(values, bins, withIds) {
513
542
  if (values.length === 0) {
514
543
  return {
515
544
  n: 0,
516
- mean: 0,
517
- p50: 0,
518
- p95: 0,
519
- stddev: 0,
520
- min: 0,
521
- max: 0,
545
+ mean: null,
546
+ p50: null,
547
+ p95: null,
548
+ stddev: null,
549
+ min: null,
550
+ max: null,
522
551
  histogram: []
523
552
  };
524
553
  }
@@ -677,41 +706,28 @@ function computeLift(runs, baselineId, candidateId, split) {
677
706
  const ids = [...new Set(runs.map((r) => r.candidateId))];
678
707
  if (ids.length !== 2) return void 0;
679
708
  const [idA, idB] = ids;
680
- const meanA = mean(runs.filter((r) => r.candidateId === idA).map((r) => compositeOf(r, split)));
681
- const meanB = mean(runs.filter((r) => r.candidateId === idB).map((r) => compositeOf(r, split)));
709
+ const scoresA = finiteCompositeScores(
710
+ runs.filter((run) => run.candidateId === idA),
711
+ split
712
+ );
713
+ const scoresB = finiteCompositeScores(
714
+ runs.filter((run) => run.candidateId === idB),
715
+ split
716
+ );
717
+ if (scoresA.length === 0 || scoresB.length === 0) return void 0;
718
+ const meanA = mean(scoresA);
719
+ const meanB = mean(scoresB);
682
720
  bId = meanA <= meanB ? idA : idB;
683
721
  cId = meanA <= meanB ? idB : idA;
684
722
  }
685
723
  const baseline = runs.filter((r) => r.candidateId === bId);
686
724
  const candidate = runs.filter((r) => r.candidateId === cId);
687
725
  if (baseline.length === 0 || candidate.length === 0) return void 0;
688
- const baselineByKey = new Map(baseline.map((r) => [pairingKey(r), r]));
689
- const pairedBaseline = [];
690
- const pairedCandidate = [];
691
- let usedKeyPairing = false;
692
- for (const cand of candidate) {
693
- const b = baselineByKey.get(pairingKey(cand));
694
- if (b) {
695
- const bC = compositeOf(b, split);
696
- const cC = compositeOf(cand, split);
697
- if (Number.isFinite(bC) && Number.isFinite(cC)) {
698
- pairedBaseline.push(bC);
699
- pairedCandidate.push(cC);
700
- usedKeyPairing = true;
701
- }
702
- }
703
- }
704
- if (!usedKeyPairing) {
705
- const n = Math.min(baseline.length, candidate.length);
706
- for (let i = 0; i < n; i++) {
707
- const bC = compositeOf(baseline[i], split);
708
- const cC = compositeOf(candidate[i], split);
709
- if (Number.isFinite(bC) && Number.isFinite(cC)) {
710
- pairedBaseline.push(bC);
711
- pairedCandidate.push(cC);
712
- }
713
- }
714
- }
726
+ const scoredBaseline = baseline.filter((run) => Number.isFinite(compositeOf(run, split)));
727
+ const scoredCandidate = candidate.filter((run) => Number.isFinite(compositeOf(run, split)));
728
+ const pairing = pairRunRecords(scoredBaseline, scoredCandidate);
729
+ const pairedBaseline = pairing.pairs.map((pair) => compositeOf(pair.baseline, split));
730
+ const pairedCandidate = pairing.pairs.map((pair) => compositeOf(pair.treatment, split));
715
731
  if (pairedBaseline.length === 0) return void 0;
716
732
  const baselineMean = mean(pairedBaseline);
717
733
  const candidateMean = mean(pairedCandidate);
@@ -722,10 +738,10 @@ function computeLift(runs, baselineId, candidateId, split) {
722
738
  statistic: "mean"
723
739
  });
724
740
  const tTest = pairedTTest(pairedBaseline, pairedCandidate);
725
- const d = cohensD(pairedBaseline, pairedCandidate);
741
+ const d = pairedCohensDz(pairedBaseline, pairedCandidate);
726
742
  const mde = pairedMde({ nPaired: pairedBaseline.length, power: 0.8, alpha: 0.05 });
727
- const requiredN = requiredSampleSize({
728
- effect: Math.max(Math.abs(delta), 1e-6),
743
+ const requiredN = d === null || d === 0 ? null : requiredPairedSampleSize({
744
+ effect: Math.abs(d),
729
745
  power: 0.8,
730
746
  alpha: 0.05
731
747
  });
@@ -736,19 +752,18 @@ function computeLift(runs, baselineId, candidateId, split) {
736
752
  ci95: [bootstrap.low, bootstrap.high],
737
753
  pValue: tTest.p,
738
754
  n: pairedBaseline.length,
755
+ unpairedBaseline: pairing.unpairedBaseline.length,
756
+ unpairedCandidate: pairing.unpairedTreatment.length,
739
757
  cohensD: d,
740
758
  mde,
741
759
  requiredN
742
760
  };
743
761
  }
744
- function pairingKey(r) {
745
- return `${r.experimentId}::${r.seed}`;
746
- }
747
762
  function mean(arr) {
748
763
  return arr.length === 0 ? 0 : arr.reduce((s, v) => s + v, 0) / arr.length;
749
764
  }
750
765
  async function computeFailureClusters(runs, analyst, split) {
751
- const failed = runs.filter((r) => compositeOf(r, split) < 0.5 || r.failureMode !== void 0);
766
+ const failed = runs.filter((run) => isTaskFailure(run, split));
752
767
  if (failed.length === 0) return { clusters: [], totalFailures: 0 };
753
768
  const clusters = /* @__PURE__ */ new Map();
754
769
  for (const run of failed) {
@@ -775,6 +790,14 @@ async function computeFailureClusters(runs, analyst, split) {
775
790
  clusterList.sort((a, b) => b.share - a.share);
776
791
  return { clusters: clusterList, totalFailures: failed.length };
777
792
  }
793
+ function finiteCompositeScores(runs, split) {
794
+ return runs.map((run) => compositeOf(run, split)).filter(Number.isFinite);
795
+ }
796
+ function isTaskFailure(run, split) {
797
+ if (run.failureClass !== void 0 && run.failureClass !== "success") return true;
798
+ const score = compositeOf(run, split);
799
+ return Number.isFinite(score) && score < 0.5;
800
+ }
778
801
  function computeContamination(runs, canaries) {
779
802
  let leaks = 0;
780
803
  const details = [];
@@ -832,24 +855,30 @@ function computeOutcomeCorrelation(runs, outcome, split) {
832
855
  }
833
856
  function buildReleaseScorecard(composite, lift, contamination) {
834
857
  const axes = [];
835
- const liftPass = lift === void 0 || lift.ci95[0] > 0 ? "pass" : lift.delta > 0 ? "warn" : "fail";
858
+ const liftPass = lift === void 0 ? "not_evaluated" : lift.ci95[0] > 0 ? "pass" : lift.delta > 0 ? "warn" : "fail";
836
859
  axes.push({
837
860
  name: "quality-lift",
838
861
  status: liftPass,
839
862
  detail: lift ? `delta=${lift.delta.toFixed(3)}, CI95=[${lift.ci95[0].toFixed(3)}, ${lift.ci95[1].toFixed(3)}], n=${lift.n}` : "no baseline/candidate pair available"
840
863
  });
841
- const contamPass = contamination === void 0 || contamination.leaks === 0 ? "pass" : "fail";
864
+ const contamPass = contamination === void 0 ? "not_evaluated" : contamination.leaks === 0 ? "pass" : "fail";
842
865
  axes.push({
843
866
  name: "contamination",
844
867
  status: contamPass,
845
868
  detail: contamination ? `${contamination.leaks} canary leak(s)` : "no canaries supplied"
846
869
  });
847
- axes.push({
848
- name: "composite-distribution",
849
- status: composite.mean >= 0.5 ? "pass" : composite.mean >= 0.3 ? "warn" : "fail",
850
- detail: `mean=${composite.mean.toFixed(3)}, p50=${composite.p50.toFixed(3)}, p95=${composite.p95.toFixed(3)} over n=${composite.n}`
851
- });
852
- const status = axes.some((a) => a.status === "fail") ? "fail" : axes.some((a) => a.status === "warn") ? "warn" : "pass";
870
+ axes.push(
871
+ composite.n === 0 ? {
872
+ name: "composite-distribution",
873
+ status: "not_evaluated",
874
+ detail: "no task-quality scores available"
875
+ } : {
876
+ name: "composite-distribution",
877
+ status: composite.mean !== null && composite.mean >= 0.5 ? "pass" : composite.mean !== null && composite.mean >= 0.3 ? "warn" : "fail",
878
+ detail: composite.mean === null || composite.p50 === null || composite.p95 === null ? "task-quality distribution is internally incomplete" : `mean=${composite.mean.toFixed(3)}, p50=${composite.p50.toFixed(3)}, p95=${composite.p95.toFixed(3)} over n=${composite.n}`
879
+ }
880
+ );
881
+ const status = axes.some((a) => a.status === "fail") ? "fail" : axes.some((a) => a.status === "warn" || a.status === "not_evaluated") ? "warn" : "pass";
853
882
  return {
854
883
  status,
855
884
  axes,
@@ -884,7 +913,7 @@ function buildRecommendations(ctx) {
884
913
  });
885
914
  }
886
915
  }
887
- if (ctx.composite.n > 0) {
916
+ if (ctx.composite.n > 0 && ctx.composite.mean !== null && ctx.composite.p50 !== null && ctx.composite.p95 !== null) {
888
917
  if (ctx.composite.mean < 0.3) {
889
918
  const tail = ctx.composite.tailRuns ?? [];
890
919
  const names = tail.slice(0, 5).map((t) => `${t.runId}=${t.score.toFixed(3)}`).join(", ");
@@ -907,15 +936,15 @@ function buildRecommendations(ctx) {
907
936
  });
908
937
  }
909
938
  }
910
- if (ctx.failureModes && ctx.failureModes.length > 0) {
911
- const top = ctx.failureModes[0];
939
+ if (ctx.failureClasses && ctx.failureClasses.length > 0) {
940
+ const top = ctx.failureClasses[0];
912
941
  if (top.count >= 3 && top.share >= 0.15) {
913
942
  out.push({
914
943
  priority: top.share >= 0.25 ? "high" : "medium",
915
944
  kind: "investigate",
916
- title: `'${top.mode}' is the dominant failure mode \u2014 ${top.count} runs (${(top.share * 100).toFixed(0)}% of the corpus)`,
917
- detail: `The mean composite can look acceptable while one named failure dominates the lower tail. ${top.count} of ${ctx.composite.n} runs failed with '${top.mode}'${ctx.failureModes.length > 1 ? ` (next: '${ctx.failureModes[1].mode}' \xD7${ctx.failureModes[1].count})` : ""}. Fix this cause first.`,
918
- evidencePath: "failureModes"
945
+ title: `'${top.failureClass}' is the dominant failure class \u2014 ${top.count} runs (${(top.share * 100).toFixed(0)}% of the corpus)`,
946
+ detail: `The mean composite can look acceptable while one failure class dominates the lower tail. ${top.count} of ${ctx.composite.n} runs failed with '${top.failureClass}'${ctx.failureClasses.length > 1 ? ` (next: '${ctx.failureClasses[1].failureClass}' \xD7${ctx.failureClasses[1].count})` : ""}. Fix this cause first.`,
947
+ evidencePath: "failureClasses"
919
948
  });
920
949
  }
921
950
  }
@@ -929,6 +958,8 @@ function buildRecommendations(ctx) {
929
958
  });
930
959
  }
931
960
  if (ctx.lift) {
961
+ const pairedEffect = ctx.lift.cohensD === null ? "undefined (zero delta variance)" : ctx.lift.cohensD.toFixed(2);
962
+ const requiredRuns = ctx.lift.requiredN === null ? "not estimable" : `~${ctx.lift.requiredN} paired runs`;
932
963
  const decisive = ctx.lift.ci95[0] > ctx.threshold;
933
964
  const inconclusive = ctx.lift.ci95[0] <= ctx.threshold && ctx.lift.ci95[1] > ctx.threshold;
934
965
  if (decisive) {
@@ -936,14 +967,14 @@ function buildRecommendations(ctx) {
936
967
  priority: "critical",
937
968
  kind: "ship",
938
969
  title: `Ship \u2014 lift ${ctx.lift.delta.toFixed(3)} (95% CI ${ctx.lift.ci95[0].toFixed(3)}..${ctx.lift.ci95[1].toFixed(3)})`,
939
- detail: `Holdout lift exceeds threshold ${ctx.threshold} with 95% bootstrap confidence (n=${ctx.lift.n}, p=${ctx.lift.pValue.toFixed(4)}, d=${ctx.lift.cohensD.toFixed(2)}).`,
970
+ detail: `Holdout lift exceeds threshold ${ctx.threshold} with 95% bootstrap confidence (n=${ctx.lift.n}, p=${ctx.lift.pValue.toFixed(4)}, paired d=${pairedEffect}).`,
940
971
  evidencePath: "lift"
941
972
  });
942
973
  } else if (inconclusive) {
943
974
  out.push({
944
975
  priority: "high",
945
976
  kind: "expand-corpus",
946
- title: `Inconclusive \u2014 need ~${ctx.lift.requiredN} paired runs (have ${ctx.lift.n}) at current effect size`,
977
+ title: `Inconclusive \u2014 required sample is ${requiredRuns} (have ${ctx.lift.n}) at current effect size`,
947
978
  detail: `CI straddles threshold. Current MDE at 80% power is ${ctx.lift.mde.toFixed(3)}; observed delta is ${ctx.lift.delta.toFixed(3)}.`,
948
979
  evidencePath: "lift"
949
980
  });
@@ -1006,4 +1037,4 @@ export {
1006
1037
  summarizeExecution,
1007
1038
  analyzeRuns
1008
1039
  };
1009
- //# sourceMappingURL=chunk-K6IAZZ6L.js.map
1040
+ //# sourceMappingURL=chunk-NACAGYSY.js.map