@tangle-network/agent-eval 0.126.7 → 0.127.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 (129) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/analyst/index.d.ts +51 -39
  4. package/dist/analyst/index.js +4 -4
  5. package/dist/belief-state/index.d.ts +34 -36
  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 -10
  9. package/dist/builder-eval/index.js +1 -1
  10. package/dist/campaign/index.d.ts +432 -146
  11. package/dist/campaign/index.js +11 -9
  12. package/dist/{chunk-ARU2PZFM.js → chunk-424XHVKV.js} +43 -23
  13. package/dist/chunk-424XHVKV.js.map +1 -0
  14. package/dist/{chunk-NUKSVU3W.js → chunk-5DTSBUL2.js} +2 -2
  15. package/dist/{chunk-BTACVO2W.js → chunk-65QETEKG.js} +542 -258
  16. package/dist/chunk-65QETEKG.js.map +1 -0
  17. package/dist/{chunk-CGG5SLH3.js → chunk-ARCFIY7U.js} +2 -2
  18. package/dist/{chunk-P22LJ3Y2.js → chunk-CLELFRQG.js} +6 -4
  19. package/dist/chunk-CLELFRQG.js.map +1 -0
  20. package/dist/{chunk-LUNF2SEL.js → chunk-DJKY2TSY.js} +3 -3
  21. package/dist/chunk-DPUHNQLN.js +232 -0
  22. package/dist/chunk-DPUHNQLN.js.map +1 -0
  23. package/dist/{chunk-IPYXE555.js → chunk-EJGRPCO3.js} +36 -13
  24. package/dist/chunk-EJGRPCO3.js.map +1 -0
  25. package/dist/{chunk-K6IAZZ6L.js → chunk-FA3MFYNA.js} +132 -110
  26. package/dist/chunk-FA3MFYNA.js.map +1 -0
  27. package/dist/chunk-IG35YDN3.js +614 -0
  28. package/dist/chunk-IG35YDN3.js.map +1 -0
  29. package/dist/{chunk-DPZAEKA6.js → chunk-IMGJZBFG.js} +84 -56
  30. package/dist/chunk-IMGJZBFG.js.map +1 -0
  31. package/dist/{chunk-H5UD2323.js → chunk-IN455XKS.js} +53 -1
  32. package/dist/chunk-IN455XKS.js.map +1 -0
  33. package/dist/{chunk-PJQFMIOX.js → chunk-MHELPNRP.js} +31 -1
  34. package/dist/chunk-MHELPNRP.js.map +1 -0
  35. package/dist/{chunk-IILEIWGW.js → chunk-NL33OXEP.js} +41 -36
  36. package/dist/chunk-NL33OXEP.js.map +1 -0
  37. package/dist/{chunk-474LBSOX.js → chunk-OD5NQ2CS.js} +41 -16
  38. package/dist/chunk-OD5NQ2CS.js.map +1 -0
  39. package/dist/{chunk-QOTFXW5L.js → chunk-P5W7RQKK.js} +2 -2
  40. package/dist/{chunk-IR3KBHOY.js → chunk-P6FYH6K4.js} +39 -3
  41. package/dist/chunk-P6FYH6K4.js.map +1 -0
  42. package/dist/{chunk-J5SQWP6Y.js → chunk-PBE2LOSS.js} +18 -2
  43. package/dist/chunk-PBE2LOSS.js.map +1 -0
  44. package/dist/{chunk-E3IADPLG.js → chunk-PRB53E3J.js} +50 -224
  45. package/dist/chunk-PRB53E3J.js.map +1 -0
  46. package/dist/{chunk-X4UCIOTZ.js → chunk-S5YLIBFX.js} +2 -2
  47. package/dist/{chunk-7VYEGHWF.js → chunk-SZLVEKMJ.js} +41 -20
  48. package/dist/chunk-SZLVEKMJ.js.map +1 -0
  49. package/dist/{chunk-LKKT3IVV.js → chunk-TSN7JT6D.js} +6 -4
  50. package/dist/{chunk-LKKT3IVV.js.map → chunk-TSN7JT6D.js.map} +1 -1
  51. package/dist/{chunk-M7AH34KV.js → chunk-UTA2TGC4.js} +32 -16
  52. package/dist/chunk-UTA2TGC4.js.map +1 -0
  53. package/dist/{chunk-MAX3TN3C.js → chunk-UWZZKKU7.js} +7 -19
  54. package/dist/chunk-UWZZKKU7.js.map +1 -0
  55. package/dist/{chunk-ZVCHKKOP.js → chunk-WED5KLZQ.js} +39 -15
  56. package/dist/chunk-WED5KLZQ.js.map +1 -0
  57. package/dist/{chunk-OCFJACJU.js → chunk-X6VOPMLD.js} +74 -14
  58. package/dist/chunk-X6VOPMLD.js.map +1 -0
  59. package/dist/{chunk-MOXWMGPC.js → chunk-Y5CYOL42.js} +159 -70
  60. package/dist/chunk-Y5CYOL42.js.map +1 -0
  61. package/dist/{chunk-FO7HEH76.js → chunk-YJBNWCAA.js} +2 -2
  62. package/dist/chunk-ZPUEEM5U.js +179 -0
  63. package/dist/chunk-ZPUEEM5U.js.map +1 -0
  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 +421 -124
  67. package/dist/contract/index.js +242 -115
  68. package/dist/contract/index.js.map +1 -1
  69. package/dist/control.d.ts +34 -32
  70. package/dist/control.js +2 -2
  71. package/dist/hosted/index.d.ts +142 -41
  72. package/dist/hosted/index.js +23 -1
  73. package/dist/index.d.ts +364 -255
  74. package/dist/index.js +315 -226
  75. package/dist/index.js.map +1 -1
  76. package/dist/meta-eval/index.d.ts +32 -32
  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 +97 -74
  82. package/dist/reporting.js +5 -4
  83. package/dist/rl.d.ts +177 -131
  84. package/dist/rl.js +232 -126
  85. package/dist/rl.js.map +1 -1
  86. package/dist/rollout/index.d.ts +53 -63
  87. package/dist/rollout/index.js +7 -5
  88. package/dist/{run-campaign-FFRM3RH5.js → run-campaign-3A2EBYBE.js} +4 -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 +66 -41
  92. package/dist/traces.js +9 -5
  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/hosted-ingest-spec.md +37 -40
  97. package/docs/insight-report.md +57 -7
  98. package/docs/research-report-methodology.md +20 -21
  99. package/docs/rollout.md +13 -8
  100. package/package.json +4 -3
  101. package/dist/chunk-474LBSOX.js.map +0 -1
  102. package/dist/chunk-7VYEGHWF.js.map +0 -1
  103. package/dist/chunk-ARU2PZFM.js.map +0 -1
  104. package/dist/chunk-BTACVO2W.js.map +0 -1
  105. package/dist/chunk-DPZAEKA6.js.map +0 -1
  106. package/dist/chunk-E3IADPLG.js.map +0 -1
  107. package/dist/chunk-H5UD2323.js.map +0 -1
  108. package/dist/chunk-IILEIWGW.js.map +0 -1
  109. package/dist/chunk-IPYXE555.js.map +0 -1
  110. package/dist/chunk-IR3KBHOY.js.map +0 -1
  111. package/dist/chunk-J5SQWP6Y.js.map +0 -1
  112. package/dist/chunk-K6IAZZ6L.js.map +0 -1
  113. package/dist/chunk-M7AH34KV.js.map +0 -1
  114. package/dist/chunk-MAX3TN3C.js.map +0 -1
  115. package/dist/chunk-MOXWMGPC.js.map +0 -1
  116. package/dist/chunk-OCFJACJU.js.map +0 -1
  117. package/dist/chunk-P22LJ3Y2.js.map +0 -1
  118. package/dist/chunk-PJQFMIOX.js.map +0 -1
  119. package/dist/chunk-ZVCHKKOP.js.map +0 -1
  120. package/dist/chunk-ZZUXHH3R.js +0 -99
  121. package/dist/chunk-ZZUXHH3R.js.map +0 -1
  122. /package/dist/{chunk-NUKSVU3W.js.map → chunk-5DTSBUL2.js.map} +0 -0
  123. /package/dist/{chunk-CGG5SLH3.js.map → chunk-ARCFIY7U.js.map} +0 -0
  124. /package/dist/{chunk-LUNF2SEL.js.map → chunk-DJKY2TSY.js.map} +0 -0
  125. /package/dist/{chunk-QOTFXW5L.js.map → chunk-P5W7RQKK.js.map} +0 -0
  126. /package/dist/{chunk-X4UCIOTZ.js.map → chunk-S5YLIBFX.js.map} +0 -0
  127. /package/dist/{chunk-FO7HEH76.js.map → chunk-YJBNWCAA.js.map} +0 -0
  128. /package/dist/{chunk-KE2VWPZX.js.map → chunk-ZUUWPZCV.js.map} +0 -0
  129. /package/dist/{run-campaign-FFRM3RH5.js.map → run-campaign-3A2EBYBE.js.map} +0 -0
@@ -13,7 +13,7 @@ import {
13
13
  runCampaign,
14
14
  summarizeAgentReceiptIntegrity,
15
15
  tryAcquireAtomicFileLock
16
- } from "./chunk-ZVCHKKOP.js";
16
+ } from "./chunk-WED5KLZQ.js";
17
17
  import {
18
18
  clamp01,
19
19
  combineAbortSignals,
@@ -21,17 +21,21 @@ import {
21
21
  } from "./chunk-WGXIEX7P.js";
22
22
  import {
23
23
  detectRewardHacking
24
- } from "./chunk-ARU2PZFM.js";
24
+ } from "./chunk-424XHVKV.js";
25
+ import {
26
+ campaignCellExecutionEvidence,
27
+ projectCampaignCellQuality
28
+ } from "./chunk-ZPUEEM5U.js";
25
29
  import {
26
30
  costReceiptFromLlm,
27
31
  costReceiptFromLlmError,
28
32
  maximumChargeForLlmRequest,
29
33
  stripFencedJson
30
- } from "./chunk-J5SQWP6Y.js";
34
+ } from "./chunk-PBE2LOSS.js";
31
35
  import {
32
36
  pairedBootstrap,
33
37
  weightedComposite
34
- } from "./chunk-PJQFMIOX.js";
38
+ } from "./chunk-MHELPNRP.js";
35
39
  import {
36
40
  CostLedger,
37
41
  costForTokenPricing
@@ -1199,11 +1203,11 @@ function renderPrBody(result, gate, diff) {
1199
1203
  lines.push("");
1200
1204
  lines.push("### Contributing gates");
1201
1205
  lines.push("");
1202
- lines.push("| gate | passed | detail |");
1206
+ lines.push("| gate | status | detail |");
1203
1207
  lines.push("|---|---|---|");
1204
1208
  for (const c of gate.contributingGates) {
1205
1209
  const detail = typeof c.detail === "object" ? JSON.stringify(c.detail).slice(0, 80) : String(c.detail).slice(0, 80);
1206
- lines.push(`| ${c.name} | ${c.passed ? "\u2713" : "\u2717"} | ${detail} |`);
1210
+ lines.push(`| ${c.name} | ${c.status} | ${detail} |`);
1207
1211
  }
1208
1212
  lines.push("");
1209
1213
  lines.push("### Promoted surface");
@@ -1505,14 +1509,18 @@ function parseReflectionResponse(raw, maxProposals) {
1505
1509
 
1506
1510
  // src/campaign/score-utils.ts
1507
1511
  function campaignMeanComposite(campaign) {
1508
- const composites = [];
1509
- for (const cell of campaign.cells) {
1510
- const cellComposites = Object.values(cell.judgeScores).filter((score) => score.failed !== true && Number.isFinite(score.composite)).map((score) => score.composite);
1511
- if (cellComposites.length > 0) {
1512
- composites.push(cellComposites.reduce((a, b) => a + b, 0) / cellComposites.length);
1513
- }
1512
+ const mean3 = campaignMeanCompositeOrNull(campaign);
1513
+ if (mean3 === null) {
1514
+ throw new Error("campaignMeanComposite: campaign has no complete cell-quality scores");
1514
1515
  }
1515
- return composites.length === 0 ? 0 : composites.reduce((a, b) => a + b, 0) / composites.length;
1516
+ return mean3;
1517
+ }
1518
+ function campaignMeanCompositeOrNull(campaign) {
1519
+ const scores = campaign.cells.flatMap((cell) => {
1520
+ const score = projectCampaignCellQuality(cell).score;
1521
+ return score === void 0 ? [] : [score];
1522
+ });
1523
+ return scores.length === 0 ? null : scores.reduce((sum, score) => sum + score, 0) / scores.length;
1516
1524
  }
1517
1525
  function assertFiniteRankKey(key, label, expectedLength) {
1518
1526
  if (!Array.isArray(key) || key.length === 0) {
@@ -1544,11 +1552,10 @@ function campaignBreakdown(campaign) {
1544
1552
  const notesByScenario = /* @__PURE__ */ new Map();
1545
1553
  const emittedByScenario = /* @__PURE__ */ new Map();
1546
1554
  for (const cell of campaign.cells) {
1547
- const judgeScores = Object.values(cell.judgeScores).filter(
1548
- (score) => score.failed !== true && Number.isFinite(score.composite)
1549
- );
1550
- if (judgeScores.length === 0) continue;
1551
- const cellComposite = judgeScores.reduce((a, s) => a + s.composite, 0) / judgeScores.length;
1555
+ const quality = projectCampaignCellQuality(cell);
1556
+ if (quality.score === void 0) continue;
1557
+ const judgeScores = Object.values(quality.successfulJudgeScores);
1558
+ const cellComposite = quality.score;
1552
1559
  const arr = byScenario.get(cell.scenarioId) ?? [];
1553
1560
  arr.push(cellComposite);
1554
1561
  byScenario.set(cell.scenarioId, arr);
@@ -1981,7 +1988,7 @@ function assertOptimizationProvenance(methodName, value) {
1981
1988
  );
1982
1989
  };
1983
1990
  if (!value || typeof value !== "object") fail("value");
1984
- if (!value.source || value.source.kind !== "package" || !["observed", "declared"].includes(value.source.evidence) || typeof value.source.package !== "string" || !value.source.package.trim() || typeof value.source.version !== "string" || !value.source.version.trim()) {
1991
+ if (value.source?.kind !== "package" || !["observed", "declared"].includes(value.source.evidence) || typeof value.source.package !== "string" || !value.source.package.trim() || typeof value.source.version !== "string" || !value.source.version.trim()) {
1985
1992
  fail("source");
1986
1993
  }
1987
1994
  for (const [field, entry] of [
@@ -3671,7 +3678,7 @@ function assertExternalTextOptimizationConfig(config) {
3671
3678
  "externalTextOptimizationMethod: maxOptimizerCostUsd must be finite and non-negative"
3672
3679
  );
3673
3680
  }
3674
- if (!config.source || config.source.kind !== "package" || typeof config.source.package !== "string" || !config.source.package.trim() || config.source.package.trim() !== config.source.package || typeof config.source.version !== "string" || !config.source.version.trim() || config.source.version.trim() !== config.source.version) {
3681
+ if (config.source?.kind !== "package" || typeof config.source.package !== "string" || !config.source.package.trim() || config.source.package.trim() !== config.source.package || typeof config.source.version !== "string" || !config.source.version.trim() || config.source.version.trim() !== config.source.version) {
3675
3682
  throw new Error("externalTextOptimizationMethod: source must identify a package and version");
3676
3683
  }
3677
3684
  for (const [field, value] of [
@@ -4213,7 +4220,13 @@ function composeGate(...gates) {
4213
4220
  const decisions = results.map((r) => r.res.decision);
4214
4221
  const overall = decisions.every((d) => d === "ship") ? "ship" : decisions.includes("arch_ceiling") ? "arch_ceiling" : decisions.includes("model_ceiling") ? "model_ceiling" : decisions.includes("hold") ? "hold" : "need_more_work";
4215
4222
  const contributing = results.flatMap(
4216
- (r) => r.res.contributingGates.length > 0 ? r.res.contributingGates : [{ name: r.gate.name, passed: r.res.decision === "ship", detail: r.res }]
4223
+ (r) => r.res.contributingGates.length > 0 ? r.res.contributingGates : [
4224
+ {
4225
+ name: r.gate.name,
4226
+ status: r.res.decision === "ship" ? "pass" : r.res.decision === "need_more_work" ? "not_evaluated" : "fail",
4227
+ detail: r.res
4228
+ }
4229
+ ]
4217
4230
  );
4218
4231
  const reasons = results.flatMap(
4219
4232
  (r) => r.res.reasons.map((reason) => `[${r.gate.name}] ${reason}`)
@@ -4230,28 +4243,36 @@ function composeGate(...gates) {
4230
4243
 
4231
4244
  // src/canary.ts
4232
4245
  function runCanaries(runs, opts = {}) {
4233
- const alerts = [
4234
- ...detectSilentFallback(runs, opts.silentFallback ?? {}),
4235
- ...detectCalibrationDrift(runs, opts.calibrationDrift ?? {}),
4236
- ...opts.distributionShift ? detectDistributionShift(runs, opts.distributionShift) : []
4246
+ const detections = [
4247
+ detectSilentFallback(runs, opts.silentFallback ?? {}),
4248
+ detectCalibrationDrift(runs, opts.calibrationDrift ?? {})
4237
4249
  ];
4250
+ if (opts.distributionShift) {
4251
+ detections.push(detectDistributionShift(runs, opts.distributionShift));
4252
+ }
4253
+ const alerts = detections.flatMap((detection) => detection.alerts);
4238
4254
  const counts = {
4239
4255
  silent_judge_fallback: 0,
4240
4256
  judge_calibration_drift: 0,
4241
4257
  distribution_shift: 0
4242
4258
  };
4243
4259
  for (const a of alerts) counts[a.kind]++;
4244
- return { alerts, counts };
4260
+ return {
4261
+ alerts,
4262
+ counts,
4263
+ evaluations: detections.map((detection) => detection.evaluation)
4264
+ };
4245
4265
  }
4246
4266
  function detectSilentFallback(runs, opts) {
4247
4267
  const constant = opts.constant ?? 0.3;
4248
4268
  const threshold = opts.consecutiveThreshold ?? 3;
4249
4269
  const eps = opts.epsilon ?? 1e-9;
4250
4270
  const alerts = [];
4271
+ let observations = 0;
4251
4272
  let streak = 0;
4252
4273
  let streakStartRunId = null;
4253
4274
  let streakValues = [];
4254
- let lastFlush = -1;
4275
+ let reportedInStreak = false;
4255
4276
  for (let i = 0; i < runs.length; i++) {
4256
4277
  const run = runs[i];
4257
4278
  const meta = run.judgeMetadata;
@@ -4259,14 +4280,16 @@ function detectSilentFallback(runs, opts) {
4259
4280
  streak = 0;
4260
4281
  streakStartRunId = null;
4261
4282
  streakValues = [];
4283
+ reportedInStreak = false;
4262
4284
  continue;
4263
4285
  }
4286
+ observations += 1;
4264
4287
  const isFallback = meta.fallback === true || Math.abs(meta.confidence - constant) <= eps;
4265
4288
  if (isFallback) {
4266
4289
  streak += 1;
4267
4290
  if (streak === 1) streakStartRunId = run.runId;
4268
4291
  streakValues.push(meta.confidence);
4269
- if (streak >= threshold && lastFlush < i) {
4292
+ if (streak >= threshold && !reportedInStreak) {
4270
4293
  alerts.push({
4271
4294
  kind: "silent_judge_fallback",
4272
4295
  severity: "error",
@@ -4279,16 +4302,28 @@ function detectSilentFallback(runs, opts) {
4279
4302
  fallbackConstant: constant
4280
4303
  }
4281
4304
  });
4282
- lastFlush = i;
4305
+ reportedInStreak = true;
4283
4306
  }
4284
4307
  } else {
4285
4308
  streak = 0;
4286
4309
  streakStartRunId = null;
4287
4310
  streakValues = [];
4288
- lastFlush = -1;
4311
+ reportedInStreak = false;
4289
4312
  }
4290
4313
  }
4291
- return alerts;
4314
+ return {
4315
+ alerts,
4316
+ evaluation: observations >= threshold ? {
4317
+ kind: "silent_judge_fallback",
4318
+ status: "evaluated",
4319
+ observations
4320
+ } : {
4321
+ kind: "silent_judge_fallback",
4322
+ status: "not_evaluated",
4323
+ observations,
4324
+ reason: `requires at least ${threshold} run(s) with judge metadata`
4325
+ }
4326
+ };
4292
4327
  }
4293
4328
  function detectCalibrationDrift(runs, opts) {
4294
4329
  const historyWindow = opts.historyWindow ?? 50;
@@ -4301,32 +4336,55 @@ function detectCalibrationDrift(runs, opts) {
4301
4336
  conf.push(r.judgeMetadata.confidence);
4302
4337
  }
4303
4338
  }
4304
- if (conf.length < minRecent + 1) return [];
4305
4339
  const recent = conf.slice(-Math.min(recentWindow, conf.length));
4306
4340
  const historical = conf.slice(0, -recent.length).slice(-historyWindow);
4307
- if (recent.length < minRecent || historical.length < minRecent) return [];
4341
+ if (recent.length < minRecent || historical.length < minRecent) {
4342
+ return {
4343
+ alerts: [],
4344
+ evaluation: {
4345
+ kind: "judge_calibration_drift",
4346
+ status: "not_evaluated",
4347
+ observations: conf.length,
4348
+ reason: `requires at least ${minRecent} recent and ${minRecent} historical confidence observation(s); found ${recent.length} recent and ${historical.length} historical`
4349
+ }
4350
+ };
4351
+ }
4308
4352
  const ks = ksTwoSample(recent, historical);
4309
4353
  const c = alpha <= 0.01 ? 1.63 : alpha <= 0.05 ? 1.36 : alpha <= 0.1 ? 1.22 : 1;
4310
4354
  const critical = c * Math.sqrt((recent.length + historical.length) / (recent.length * historical.length));
4311
4355
  if (ks.d > critical) {
4312
- return [
4313
- {
4314
- kind: "judge_calibration_drift",
4315
- severity: "warn",
4316
- message: `judge calibration drift: KS D=${ks.d.toFixed(4)} exceeds critical=${critical.toFixed(4)} at alpha=${alpha} (recent n=${recent.length}, history n=${historical.length})`,
4317
- evidence: {
4318
- ksD: ks.d,
4319
- critical,
4320
- alpha,
4321
- recentN: recent.length,
4322
- historyN: historical.length,
4323
- recentMean: mean2(recent),
4324
- historyMean: mean2(historical)
4356
+ return {
4357
+ alerts: [
4358
+ {
4359
+ kind: "judge_calibration_drift",
4360
+ severity: "warn",
4361
+ message: `judge calibration drift: KS D=${ks.d.toFixed(4)} exceeds critical=${critical.toFixed(4)} at alpha=${alpha} (recent n=${recent.length}, history n=${historical.length})`,
4362
+ evidence: {
4363
+ ksD: ks.d,
4364
+ critical,
4365
+ alpha,
4366
+ recentN: recent.length,
4367
+ historyN: historical.length,
4368
+ recentMean: mean2(recent),
4369
+ historyMean: mean2(historical)
4370
+ }
4325
4371
  }
4372
+ ],
4373
+ evaluation: {
4374
+ kind: "judge_calibration_drift",
4375
+ status: "evaluated",
4376
+ observations: recent.length + historical.length
4326
4377
  }
4327
- ];
4378
+ };
4328
4379
  }
4329
- return [];
4380
+ return {
4381
+ alerts: [],
4382
+ evaluation: {
4383
+ kind: "judge_calibration_drift",
4384
+ status: "evaluated",
4385
+ observations: recent.length + historical.length
4386
+ }
4387
+ };
4330
4388
  }
4331
4389
  function ksTwoSample(a, b) {
4332
4390
  const sortedA = [...a].sort((x, y) => x - y);
@@ -4357,10 +4415,19 @@ function detectDistributionShift(runs, opts) {
4357
4415
  const b = cat(r);
4358
4416
  if (typeof b === "string" && b.length > 0) cats.push({ run: r, bucket: b });
4359
4417
  }
4360
- if (cats.length < minRecent + 1) return [];
4361
4418
  const recent = cats.slice(-Math.min(recentWindow, cats.length));
4362
4419
  const historical = cats.slice(0, -recent.length).slice(-historyWindow);
4363
- if (recent.length < minRecent || historical.length < minRecent) return [];
4420
+ if (recent.length < minRecent || historical.length < minRecent) {
4421
+ return {
4422
+ alerts: [],
4423
+ evaluation: {
4424
+ kind: "distribution_shift",
4425
+ status: "not_evaluated",
4426
+ observations: cats.length,
4427
+ reason: `requires at least ${minRecent} recent and ${minRecent} historical categorized run(s); found ${recent.length} recent and ${historical.length} historical`
4428
+ }
4429
+ };
4430
+ }
4364
4431
  const buckets = /* @__PURE__ */ new Set();
4365
4432
  for (const r of recent) buckets.add(r.bucket);
4366
4433
  for (const h of historical) buckets.add(h.bucket);
@@ -4385,25 +4452,39 @@ function detectDistributionShift(runs, opts) {
4385
4452
  df = Math.max(1, df - 1);
4386
4453
  const critical = chiSquareCritical(df, alpha);
4387
4454
  if (chi > critical) {
4388
- return [
4389
- {
4390
- kind: "distribution_shift",
4391
- severity: "warn",
4392
- message: `eval-set distribution shift: \u03C7\xB2=${chi.toFixed(2)} df=${df} exceeds critical=${critical.toFixed(2)} at alpha=${alpha}`,
4393
- evidence: {
4394
- chi,
4395
- df,
4396
- critical,
4397
- alpha,
4398
- recentCounts,
4399
- historicalCounts: histCounts,
4400
- recentN: recent.length,
4401
- historyN: historical.length
4455
+ return {
4456
+ alerts: [
4457
+ {
4458
+ kind: "distribution_shift",
4459
+ severity: "warn",
4460
+ message: `eval-set distribution shift: \u03C7\xB2=${chi.toFixed(2)} df=${df} exceeds critical=${critical.toFixed(2)} at alpha=${alpha}`,
4461
+ evidence: {
4462
+ chi,
4463
+ df,
4464
+ critical,
4465
+ alpha,
4466
+ recentCounts,
4467
+ historicalCounts: histCounts,
4468
+ recentN: recent.length,
4469
+ historyN: historical.length
4470
+ }
4402
4471
  }
4472
+ ],
4473
+ evaluation: {
4474
+ kind: "distribution_shift",
4475
+ status: "evaluated",
4476
+ observations: recent.length + historical.length
4403
4477
  }
4404
- ];
4478
+ };
4405
4479
  }
4406
- return [];
4480
+ return {
4481
+ alerts: [],
4482
+ evaluation: {
4483
+ kind: "distribution_shift",
4484
+ status: "evaluated",
4485
+ observations: recent.length + historical.length
4486
+ }
4487
+ };
4407
4488
  }
4408
4489
  function chiSquareCritical(df, alpha) {
4409
4490
  const TABLE = {
@@ -4555,150 +4636,289 @@ function defaultProductionGate(options) {
4555
4636
  const seed = options.bootstrapSeed ?? 1337;
4556
4637
  const minProductiveRuns = options.minProductiveRuns ?? 3;
4557
4638
  const heldoutStatistic = options.heldoutStatistic ?? "mean";
4558
- const blockOnGaming = options.blockOnRewardHackingGaming ?? true;
4639
+ const explicitlyRequired = new Set(options.requiredChecks ?? []);
4559
4640
  return {
4560
4641
  name: "defaultProductionGate",
4561
4642
  async decide(ctx) {
4562
4643
  const reasons = [];
4563
4644
  const contributing = [];
4645
+ const requiredUnavailable = /* @__PURE__ */ new Set();
4646
+ const unavailable = (name, required, reason, detail = {}) => {
4647
+ contributing.push({
4648
+ name,
4649
+ status: "not_evaluated",
4650
+ detail: { reason, required, ...detail }
4651
+ });
4652
+ if (required) {
4653
+ requiredUnavailable.add(name);
4654
+ reasons.push(`${name}: ${reason}`);
4655
+ }
4656
+ };
4564
4657
  const scenarioIds = new Set(options.holdoutScenarios.map((s) => s.id));
4565
- const sig = heldoutSignificance(
4566
- pairHoldout(
4658
+ let delta;
4659
+ if (!ctx.baselineJudgeScores) {
4660
+ unavailable(
4661
+ "heldout-significance",
4662
+ true,
4663
+ "baselineJudgeScores is required for a held-out comparison"
4664
+ );
4665
+ } else {
4666
+ const sig = heldoutSignificance(
4667
+ pairHoldout(ctx.judgeScores, ctx.baselineJudgeScores, scenarioIds, (s) => s.composite),
4668
+ {
4669
+ deltaThreshold,
4670
+ minProductiveRuns,
4671
+ confidence,
4672
+ resamples,
4673
+ seed,
4674
+ statistic: heldoutStatistic
4675
+ }
4676
+ );
4677
+ delta = heldoutStatistic === "median" ? sig.bootstrap.median : sig.bootstrap.mean;
4678
+ const heldoutPass = sig.significant;
4679
+ contributing.push({
4680
+ name: "heldout-significance",
4681
+ status: sig.fewRuns ? "not_evaluated" : heldoutPass ? "pass" : "fail",
4682
+ detail: {
4683
+ n: sig.n,
4684
+ delta,
4685
+ deltaMean: sig.bootstrap.mean,
4686
+ deltaMedianDiagnostic: sig.medianBootstrap.median,
4687
+ deltaMedian: sig.medianBootstrap.median,
4688
+ tieFraction: sig.tieFraction,
4689
+ ciLow: sig.bootstrap.low,
4690
+ ciHigh: sig.bootstrap.high,
4691
+ confidence: sig.bootstrap.confidence,
4692
+ deltaThreshold,
4693
+ fewRuns: sig.fewRuns
4694
+ }
4695
+ });
4696
+ if (sig.fewRuns) {
4697
+ requiredUnavailable.add("heldout-significance");
4698
+ }
4699
+ if (!heldoutPass) {
4700
+ const tieNote = sig.tieFraction >= TIE_WARN_FRACTION ? `; ${(sig.tieFraction * 100).toFixed(0)}% tied scenarios` : "";
4701
+ reasons.push(
4702
+ sig.fewRuns ? `held-out: only ${sig.n} paired runs (< ${minProductiveRuns}) \u2014 too few to claim significance` : `held-out CI.low ${sig.bootstrap.low.toFixed(3)} \u2264 threshold ${deltaThreshold} (${heldoutStatistic} \u0394 ${delta.toFixed(3)}, ${(sig.bootstrap.confidence * 100).toFixed(0)}% CI [${sig.bootstrap.low.toFixed(3)}, ${sig.bootstrap.high.toFixed(3)}]${tieNote})`
4703
+ );
4704
+ }
4705
+ }
4706
+ const dimensionsProvided = options.criticalDimensions !== void 0;
4707
+ const criticalDimensions = [...new Set(options.criticalDimensions ?? [])];
4708
+ const dimensionsRequired = dimensionsProvided || explicitlyRequired.has("dimension-regression");
4709
+ if (!dimensionsProvided) {
4710
+ unavailable(
4711
+ "dimension-regression",
4712
+ dimensionsRequired,
4713
+ "criticalDimensions is not configured"
4714
+ );
4715
+ } else if (criticalDimensions.length === 0) {
4716
+ unavailable(
4717
+ "dimension-regression",
4718
+ true,
4719
+ "criticalDimensions must contain at least one dimension"
4720
+ );
4721
+ } else if (!ctx.baselineJudgeScores) {
4722
+ unavailable(
4723
+ "dimension-regression",
4724
+ true,
4725
+ "baselineJudgeScores is required to measure dimension regressions",
4726
+ { guarded: criticalDimensions }
4727
+ );
4728
+ } else {
4729
+ const dimRegs = dimensionRegressions(
4567
4730
  ctx.judgeScores,
4568
- ctx.baselineJudgeScores ?? ctx.judgeScores,
4731
+ ctx.baselineJudgeScores,
4569
4732
  scenarioIds,
4570
- (s) => s.composite
4571
- ),
4572
- {
4573
- deltaThreshold,
4574
- minProductiveRuns,
4575
- confidence,
4576
- resamples,
4577
- seed,
4578
- statistic: heldoutStatistic
4733
+ criticalDimensions,
4734
+ { tolerance: options.regressionTolerance, confidence, resamples, seed }
4735
+ );
4736
+ const measured = new Set(dimRegs.map((result) => result.dimension));
4737
+ const missingDimensions = criticalDimensions.filter((dimension) => !measured.has(dimension));
4738
+ const regressed = dimRegs.filter((result) => result.regressed);
4739
+ const dimensionStatus = regressed.length > 0 ? "fail" : missingDimensions.length > 0 ? "not_evaluated" : "pass";
4740
+ contributing.push({
4741
+ name: "dimension-regression",
4742
+ status: dimensionStatus,
4743
+ detail: {
4744
+ guarded: criticalDimensions,
4745
+ missingDimensions,
4746
+ regressions: dimRegs.map((result) => ({
4747
+ dimension: result.dimension,
4748
+ ciLow: result.bootstrap.low,
4749
+ median: result.bootstrap.median,
4750
+ tolerance: result.tolerance,
4751
+ n: result.n,
4752
+ regressed: result.regressed
4753
+ }))
4754
+ }
4755
+ });
4756
+ if (missingDimensions.length > 0) {
4757
+ requiredUnavailable.add("dimension-regression");
4758
+ reasons.push(`critical dimension(s) were not scored: ${missingDimensions.join(", ")}`);
4579
4759
  }
4580
- );
4581
- const delta = heldoutStatistic === "median" ? sig.bootstrap.median : sig.bootstrap.mean;
4582
- const heldoutPass = sig.significant;
4583
- contributing.push({
4584
- name: "heldout-significance",
4585
- passed: heldoutPass,
4586
- detail: {
4587
- n: sig.n,
4588
- delta,
4589
- deltaMean: sig.bootstrap.mean,
4590
- deltaMedianDiagnostic: sig.medianBootstrap.median,
4591
- // Back-compat: prior consumers read `deltaMedian`. It now always carries
4592
- // the median diagnostic (the ship decision keys on `delta`/mean).
4593
- deltaMedian: sig.medianBootstrap.median,
4594
- tieFraction: sig.tieFraction,
4595
- ciLow: sig.bootstrap.low,
4596
- ciHigh: sig.bootstrap.high,
4597
- confidence: sig.bootstrap.confidence,
4598
- deltaThreshold,
4599
- fewRuns: sig.fewRuns
4760
+ if (regressed.length > 0) {
4761
+ reasons.push(
4762
+ `critical dimension(s) regressed: ${regressed.map((result) => `${result.dimension} CI.low ${result.bootstrap.low.toFixed(3)} < -${result.tolerance}`).join("; ")}`
4763
+ );
4600
4764
  }
4601
- });
4602
- if (!heldoutPass) {
4603
- const tieNote = sig.tieFraction >= TIE_WARN_FRACTION ? `; ${(sig.tieFraction * 100).toFixed(0)}% tied scenarios` : "";
4604
- reasons.push(
4605
- sig.fewRuns ? `held-out: only ${sig.n} paired runs (< ${minProductiveRuns}) \u2014 too few to claim significance` : `held-out CI.low ${sig.bootstrap.low.toFixed(3)} \u2264 threshold ${deltaThreshold} (${heldoutStatistic} \u0394 ${delta.toFixed(3)}, ${(sig.bootstrap.confidence * 100).toFixed(0)}% CI [${sig.bootstrap.low.toFixed(3)}, ${sig.bootstrap.high.toFixed(3)}]${tieNote})`
4606
- );
4607
4765
  }
4608
- const dimRegs = options.criticalDimensions?.length ? dimensionRegressions(
4609
- ctx.judgeScores,
4610
- ctx.baselineJudgeScores ?? ctx.judgeScores,
4611
- scenarioIds,
4612
- options.criticalDimensions,
4613
- { tolerance: options.regressionTolerance, confidence, resamples, seed }
4614
- ) : [];
4615
- const regressed = dimRegs.filter((d) => d.regressed);
4616
- const dimPass = regressed.length === 0;
4617
- contributing.push({
4618
- name: "dimension-regression",
4619
- passed: dimPass,
4620
- detail: {
4621
- guarded: options.criticalDimensions ?? [],
4622
- regressions: dimRegs.map((d) => ({
4623
- dimension: d.dimension,
4624
- ciLow: d.bootstrap.low,
4625
- median: d.bootstrap.median,
4626
- tolerance: d.tolerance,
4627
- n: d.n,
4628
- regressed: d.regressed
4629
- }))
4766
+ const budgetUsd = options.budgetUsd;
4767
+ const budgetConfigured = budgetUsd !== void 0;
4768
+ const budgetRequired = budgetConfigured || explicitlyRequired.has("budget");
4769
+ if (!budgetConfigured) {
4770
+ unavailable("budget", budgetRequired, "budgetUsd is not configured");
4771
+ } else if (!ctx.costLedger) {
4772
+ unavailable("budget", true, "costLedger is required to measure complete run spend", {
4773
+ budgetUsd
4774
+ });
4775
+ } else {
4776
+ const cost = ctx.costLedger.summary();
4777
+ const accountingComplete = cost.accountingComplete && cost.pendingCalls === 0 && cost.unresolvedCalls === 0;
4778
+ const budgetPass = cost.totalCostUsd <= budgetUsd;
4779
+ contributing.push({
4780
+ name: "budget",
4781
+ status: !accountingComplete ? "not_evaluated" : budgetPass ? "pass" : "fail",
4782
+ detail: {
4783
+ totalCostUsd: cost.totalCostUsd,
4784
+ budgetUsd,
4785
+ accountingComplete,
4786
+ pendingCalls: cost.pendingCalls,
4787
+ unresolvedCalls: cost.unresolvedCalls
4788
+ }
4789
+ });
4790
+ if (!accountingComplete) {
4791
+ requiredUnavailable.add("budget");
4792
+ reasons.push("budget: cost accounting is incomplete");
4793
+ } else if (!budgetPass) {
4794
+ reasons.push(`spend ${cost.totalCostUsd.toFixed(2)} > budget ${budgetUsd}`);
4630
4795
  }
4631
- });
4632
- if (!dimPass) {
4633
- reasons.push(
4634
- `critical dimension(s) regressed: ${regressed.map((d) => `${d.dimension} CI.low ${d.bootstrap.low.toFixed(3)} < -${d.tolerance}`).join("; ")}`
4635
- );
4636
4796
  }
4637
- const budgetPass = options.budgetUsd === void 0 || ctx.cost.candidate + ctx.cost.baseline <= options.budgetUsd;
4638
- contributing.push({
4639
- name: "budget",
4640
- passed: budgetPass,
4641
- detail: {
4642
- candidateUsd: ctx.cost.candidate,
4643
- baselineUsd: ctx.cost.baseline,
4644
- ...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd }
4797
+ const redTeamConfigured = options.redTeamBattery !== void 0;
4798
+ const redTeamRequired = redTeamConfigured || explicitlyRequired.has("red-team");
4799
+ if (!options.redTeamBattery) {
4800
+ unavailable("red-team", redTeamRequired, "redTeamBattery is not configured");
4801
+ } else if (options.redTeamBattery.length === 0) {
4802
+ unavailable("red-team", true, "redTeamBattery must contain at least one case");
4803
+ } else {
4804
+ const redTeam = probeRedTeam(ctx.candidateArtifacts, options.redTeamBattery);
4805
+ const incomplete = redTeam.unsupportedCases.length > 0 || redTeam.unscoredArtifacts.length > 0 || redTeam.evaluatedCases === 0;
4806
+ const redTeamStatus = redTeam.findings.length > 0 ? "fail" : incomplete ? "not_evaluated" : "pass";
4807
+ contributing.push({
4808
+ name: "red-team",
4809
+ status: redTeamStatus,
4810
+ detail: {
4811
+ evaluatedCases: redTeam.evaluatedCases,
4812
+ unsupportedCases: redTeam.unsupportedCases,
4813
+ unscoredArtifacts: redTeam.unscoredArtifacts,
4814
+ failures: redTeam.findings.length,
4815
+ sample: redTeam.findings.slice(0, 3)
4816
+ }
4817
+ });
4818
+ if (incomplete) {
4819
+ requiredUnavailable.add("red-team");
4645
4820
  }
4646
- });
4647
- if (!budgetPass) {
4648
- reasons.push(
4649
- `spend ${(ctx.cost.candidate + ctx.cost.baseline).toFixed(2)} > budget ${options.budgetUsd}`
4650
- );
4651
- }
4652
- const redTeamFindings = options.redTeamBattery ? probeRedTeam(ctx.candidateArtifacts, options.redTeamBattery) : { passed: true, findings: [] };
4653
- contributing.push({
4654
- name: "red-team",
4655
- passed: redTeamFindings.passed,
4656
- detail: {
4657
- failures: redTeamFindings.findings.length,
4658
- sample: redTeamFindings.findings.slice(0, 3)
4821
+ if (redTeam.unsupportedCases.length > 0) {
4822
+ reasons.push(
4823
+ `red-team: static artifact screening cannot evaluate case(s): ${redTeam.unsupportedCases.join(", ")}`
4824
+ );
4825
+ }
4826
+ if (redTeam.unscoredArtifacts.length > 0) {
4827
+ reasons.push(
4828
+ `red-team: no text could be extracted from artifact(s): ${redTeam.unscoredArtifacts.join(", ")}`
4829
+ );
4830
+ }
4831
+ if (redTeam.evaluatedCases === 0) {
4832
+ reasons.push("red-team: no artifact/case pair was evaluated");
4833
+ }
4834
+ if (redTeam.findings.length > 0) {
4835
+ reasons.push(`red-team probe failed (${redTeam.findings.length} findings)`);
4659
4836
  }
4660
- });
4661
- if (!redTeamFindings.passed) {
4662
- reasons.push(`red-team probe failed (${redTeamFindings.findings.length} findings)`);
4663
- }
4664
- let rewardHackingReport = null;
4665
- if (options.recentRuns && options.recentRuns.length >= 10) {
4666
- rewardHackingReport = detectRewardHacking({ runs: options.recentRuns });
4667
4837
  }
4668
- const gamingThreshold = 0.6;
4669
- const gamingFindings = (rewardHackingReport?.findings ?? []).filter(
4670
- (f) => f.severity >= gamingThreshold
4671
- );
4672
- const rewardHackingPass = !rewardHackingReport || !blockOnGaming || gamingFindings.length === 0 && rewardHackingReport.verdict !== "gaming";
4673
- contributing.push({
4674
- name: "reward-hacking",
4675
- passed: rewardHackingPass,
4676
- detail: { report: rewardHackingReport, gamingFindingCount: gamingFindings.length }
4677
- });
4678
- if (!rewardHackingPass) {
4679
- reasons.push(
4680
- `reward-hacking detector flagged ${gamingFindings.length} gaming-severity findings (verdict=${rewardHackingReport.verdict})`
4838
+ const rewardHacking = options.rewardHacking;
4839
+ const rewardHackingConfigured = rewardHacking !== void 0;
4840
+ const rewardHackingRequired = rewardHackingConfigured || explicitlyRequired.has("reward-hacking");
4841
+ if (!rewardHackingConfigured) {
4842
+ unavailable("reward-hacking", rewardHackingRequired, "rewardHacking is not configured");
4843
+ } else if (!options.recentRuns) {
4844
+ unavailable("reward-hacking", rewardHackingRequired, "recentRuns is not configured");
4845
+ } else if (options.recentRuns.length < 10) {
4846
+ unavailable(
4847
+ "reward-hacking",
4848
+ rewardHackingRequired,
4849
+ `recentRuns has ${options.recentRuns.length} record(s); at least 10 are required`
4681
4850
  );
4851
+ } else {
4852
+ const rewardHackingReport = detectRewardHacking({
4853
+ runs: options.recentRuns,
4854
+ ...rewardHacking
4855
+ });
4856
+ if (!rewardHackingReport.evaluatedSignals.includes("reward_divergence")) {
4857
+ unavailable(
4858
+ "reward-hacking",
4859
+ true,
4860
+ "independent proxy/truth evidence is insufficient to evaluate reward divergence",
4861
+ { report: rewardHackingReport }
4862
+ );
4863
+ } else {
4864
+ const gamingThreshold = rewardHacking.thresholds?.gaming ?? 0.6;
4865
+ const gamingFindings = rewardHackingReport.findings.filter(
4866
+ (finding) => finding.severity >= gamingThreshold
4867
+ );
4868
+ const rewardHackingPass = gamingFindings.length === 0 && rewardHackingReport.verdict !== "gaming";
4869
+ contributing.push({
4870
+ name: "reward-hacking",
4871
+ status: rewardHackingPass ? "pass" : "fail",
4872
+ detail: { report: rewardHackingReport, gamingFindingCount: gamingFindings.length }
4873
+ });
4874
+ if (!rewardHackingPass) {
4875
+ reasons.push(
4876
+ `reward-hacking detector flagged ${gamingFindings.length} gaming-severity findings (verdict=${rewardHackingReport.verdict})`
4877
+ );
4878
+ }
4879
+ }
4682
4880
  }
4683
- let canaryReport = null;
4684
- if (options.recentRuns && options.recentRuns.length >= 10) {
4685
- canaryReport = runCanaries(options.recentRuns, {});
4686
- }
4687
- const errorAlerts = (canaryReport?.alerts ?? []).filter((a) => a.severity === "error");
4688
- const canaryPass = errorAlerts.length === 0;
4689
- contributing.push({
4690
- name: "canary",
4691
- passed: canaryPass,
4692
- detail: { totalAlerts: canaryReport?.alerts.length ?? 0, errorAlerts: errorAlerts.length }
4693
- });
4694
- if (!canaryPass) {
4695
- reasons.push(`canary error alerts: ${errorAlerts.length}`);
4881
+ const canary = options.canary;
4882
+ const canaryConfigured = canary !== void 0;
4883
+ const canaryRequired = canaryConfigured || explicitlyRequired.has("canary");
4884
+ if (!canaryConfigured && !canaryRequired) {
4885
+ unavailable("canary", false, "canary is not configured");
4886
+ } else if (!options.recentRuns) {
4887
+ unavailable("canary", canaryRequired, "recentRuns is not configured");
4888
+ } else {
4889
+ const canaryReport = runCanaries(options.recentRuns, canary ?? {});
4890
+ const incomplete = canaryReport.evaluations.filter(
4891
+ (evaluation) => evaluation.status === "not_evaluated"
4892
+ );
4893
+ if (incomplete.length > 0) {
4894
+ unavailable(
4895
+ "canary",
4896
+ true,
4897
+ `insufficient evidence for: ${incomplete.map((evaluation) => evaluation.kind).join(", ")}`,
4898
+ { report: canaryReport }
4899
+ );
4900
+ } else {
4901
+ const errorAlerts = canaryReport.alerts.filter((alert) => alert.severity === "error");
4902
+ const canaryPass = errorAlerts.length === 0;
4903
+ contributing.push({
4904
+ name: "canary",
4905
+ status: canaryPass ? "pass" : "fail",
4906
+ detail: {
4907
+ totalAlerts: canaryReport.alerts.length,
4908
+ errorAlerts: errorAlerts.length,
4909
+ report: canaryReport
4910
+ }
4911
+ });
4912
+ if (!canaryPass) {
4913
+ reasons.push(`canary error alerts: ${errorAlerts.length}`);
4914
+ }
4915
+ }
4696
4916
  }
4697
- const allPassed = contributing.every((c) => c.passed);
4698
- const decision = allPassed ? "ship" : "hold";
4917
+ const decision = contributing.some((check) => check.status === "fail") || requiredUnavailable.size > 0 ? "hold" : "ship";
4918
+ const notEvaluated = contributing.filter((check) => check.status === "not_evaluated").map((check) => check.name);
4699
4919
  return {
4700
4920
  decision,
4701
- reasons: reasons.length > 0 ? reasons : ["all gates passed"],
4921
+ reasons: reasons.length > 0 ? reasons : notEvaluated.length > 0 ? [`evaluated checks passed; not evaluated: ${notEvaluated.join(", ")}`] : ["all checks passed"],
4702
4922
  contributingGates: contributing,
4703
4923
  delta
4704
4924
  };
@@ -4706,18 +4926,32 @@ function defaultProductionGate(options) {
4706
4926
  };
4707
4927
  }
4708
4928
  function probeRedTeam(artifacts, battery) {
4929
+ const supportedCases = battery.filter(
4930
+ (testCase) => testCase.payload.expected === "ignore" && !testCase.payload.forbiddenTools?.length
4931
+ );
4932
+ const unsupportedCases = battery.filter((testCase) => !supportedCases.includes(testCase)).map((testCase) => testCase.id);
4709
4933
  const findings = [];
4710
- for (const [_cellId, artifact] of artifacts) {
4934
+ const unscoredArtifacts = [];
4935
+ let evaluatedCases = 0;
4936
+ for (const [cellId, artifact] of artifacts) {
4711
4937
  const text = extractText(artifact);
4712
- if (text === void 0) continue;
4713
- for (const rtCase of battery) {
4938
+ if (text === void 0) {
4939
+ unscoredArtifacts.push(cellId);
4940
+ continue;
4941
+ }
4942
+ for (const rtCase of supportedCases) {
4943
+ evaluatedCases += 1;
4714
4944
  const finding = scoreRedTeamOutput(text, [], rtCase);
4715
4945
  if (!finding.passed) {
4716
- findings.push({ scenarioId: rtCase.id, reason: finding.reason ?? "red-team probe failed" });
4946
+ findings.push({
4947
+ cellId,
4948
+ scenarioId: rtCase.id,
4949
+ reason: finding.reason ?? "red-team probe failed"
4950
+ });
4717
4951
  }
4718
4952
  }
4719
4953
  }
4720
- return { passed: findings.length === 0, findings };
4954
+ return { evaluatedCases, findings, unsupportedCases, unscoredArtifacts };
4721
4955
  }
4722
4956
  function extractText(artifact) {
4723
4957
  if (typeof artifact === "string") return artifact;
@@ -4758,6 +4992,7 @@ function heldOutGate(options) {
4758
4992
  );
4759
4993
  const delta = sig.bootstrap.mean;
4760
4994
  const passed = sig.significant;
4995
+ const status = sig.fewRuns ? "not_evaluated" : passed ? "pass" : "fail";
4761
4996
  const tieNote = sig.tieFraction >= TIE_WARN_FRACTION ? `, ${(sig.tieFraction * 100).toFixed(0)}% tied` : "";
4762
4997
  const ci = `${(sig.bootstrap.confidence * 100).toFixed(0)}% CI [${sig.bootstrap.low.toFixed(3)}, ${sig.bootstrap.high.toFixed(3)}]`;
4763
4998
  return {
@@ -4770,7 +5005,7 @@ function heldOutGate(options) {
4770
5005
  contributingGates: [
4771
5006
  {
4772
5007
  name: "heldOutGate",
4773
- passed,
5008
+ status,
4774
5009
  detail: {
4775
5010
  deltaMean: delta,
4776
5011
  deltaMedianDiagnostic: sig.medianBootstrap.median,
@@ -4885,7 +5120,7 @@ function buildEvidenceVector(ctx, objectives, opts = {}) {
4885
5120
  var paretoPolicy = (ev) => {
4886
5121
  const contributingGates = ev.axes.map((ax) => ({
4887
5122
  name: `objective:${ax.name}`,
4888
- passed: ax.verdict === "improved",
5123
+ status: ax.verdict === "regressed" ? "fail" : ax.verdict === "few_runs" ? "not_evaluated" : "pass",
4889
5124
  detail: {
4890
5125
  direction: ax.direction,
4891
5126
  source: ax.source,
@@ -5871,19 +6106,21 @@ async function runOptimization(opts) {
5871
6106
  dispatch: (scenario, ctx) => opts.dispatchWithSurface(surface, scenario, ctx),
5872
6107
  runDir: `${opts.runDir}/gen-${gen}/candidate-${i}`
5873
6108
  });
5874
- const composite = campaignMeanComposite(campaign);
5875
- const rankKey = selectionRankKey(campaign);
5876
- assertFiniteRankKey(
5877
- rankKey,
5878
- `selectionRankKey for generation ${gen} candidate ${i}`,
5879
- winnerRankKey.length
5880
- );
5881
6109
  const coverage = campaignCoverage(
5882
6110
  campaign.cells,
5883
6111
  opts.scenarios,
5884
6112
  opts.reps ?? 1,
5885
6113
  requireJudgeScore
5886
6114
  );
6115
+ const composite = campaignMeanCompositeOrNull(campaign);
6116
+ const rankKey = coverage.complete ? selectionRankKey(campaign) : null;
6117
+ if (rankKey) {
6118
+ assertFiniteRankKey(
6119
+ rankKey,
6120
+ `selectionRankKey for generation ${gen} candidate ${i}`,
6121
+ winnerRankKey.length
6122
+ );
6123
+ }
5887
6124
  return {
5888
6125
  surfaceHash: hash,
5889
6126
  surface,
@@ -5906,9 +6143,12 @@ async function runOptimization(opts) {
5906
6143
  }
5907
6144
  surfaceResults.sort((a, b) => {
5908
6145
  if (a.coverage.complete !== b.coverage.complete) return a.coverage.complete ? -1 : 1;
5909
- return compareRankKeys(b.rankKey, a.rankKey);
6146
+ if (a.rankKey && b.rankKey) return compareRankKeys(b.rankKey, a.rankKey);
6147
+ return a.surfaceHash.localeCompare(b.surfaceHash);
5910
6148
  });
5911
- const eligibleResults = surfaceResults.filter((result) => result.coverage.complete);
6149
+ const eligibleResults = surfaceResults.filter(
6150
+ (result) => result.coverage.complete && result.composite !== null && result.rankKey !== null
6151
+ );
5912
6152
  const top = eligibleResults[0];
5913
6153
  const promoted = top && compareRankKeys(top.rankKey, winnerRankKey) > 0 ? [top] : [];
5914
6154
  if (promoted[0]) {
@@ -5928,7 +6168,7 @@ async function runOptimization(opts) {
5928
6168
  const candidate = {
5929
6169
  surfaceHash: s.surfaceHash,
5930
6170
  composite: s.composite,
5931
- ci95: [s.composite, s.composite],
6171
+ ci95: s.composite === null ? null : [s.composite, s.composite],
5932
6172
  parentSurfaceHash,
5933
6173
  parentComposite,
5934
6174
  ...s.coverage.complete ? { observedDeltaFromParent: s.composite - parentComposite } : {},
@@ -6109,7 +6349,7 @@ async function runImprovementLoop(opts) {
6109
6349
  const dispatchTimeoutMs = opts.dispatchTimeoutMs ?? DEFAULT_DISPATCH_TIMEOUT_MS;
6110
6350
  const optimization = await runOptimization({ ...opts, dispatchTimeoutMs, costLedger });
6111
6351
  const winnerIsBaseline = optimization.winnerSurfaceHash === surfaceHash(opts.baselineSurface);
6112
- const { runCampaign: runCampaign2 } = await import("./run-campaign-FFRM3RH5.js");
6352
+ const { runCampaign: runCampaign2 } = await import("./run-campaign-3A2EBYBE.js");
6113
6353
  const holdoutDeferred = (opts.holdout ?? "measured") === "deferred";
6114
6354
  const baselineOnHoldout = holdoutDeferred ? await runCampaign2({
6115
6355
  ...opts,
@@ -6200,7 +6440,11 @@ async function runImprovementLoop(opts) {
6200
6440
  "holdout deferred \u2014 improvement-set search completed without a held-out measurement; nothing to promote from this run"
6201
6441
  ],
6202
6442
  contributingGates: [
6203
- { name: "holdout-deferred", passed: false, detail: { holdout: "deferred" } }
6443
+ {
6444
+ name: "holdout-deferred",
6445
+ status: "not_evaluated",
6446
+ detail: { holdout: "deferred" }
6447
+ }
6204
6448
  ]
6205
6449
  } : winnerIsBaseline ? {
6206
6450
  decision: "hold",
@@ -6208,7 +6452,7 @@ async function runImprovementLoop(opts) {
6208
6452
  "no candidate beat the training baseline \u2014 winner == baseline (empty diff); nothing to promote"
6209
6453
  ],
6210
6454
  contributingGates: [
6211
- { name: "no-op-guard", passed: false, detail: { winnerIsBaseline: true } }
6455
+ { name: "no-op-guard", status: "fail", detail: { winnerIsBaseline: true } }
6212
6456
  ],
6213
6457
  delta: 0
6214
6458
  } : await opts.gate.decide({
@@ -6289,13 +6533,7 @@ function loopProvenanceArgsFromResult(input) {
6289
6533
  };
6290
6534
  }
6291
6535
  function meanHoldoutComposite(campaign) {
6292
- const xs = [];
6293
- for (const cell of campaign.cells) {
6294
- if (cell.error) continue;
6295
- const cs = Object.values(cell.judgeScores).map((s) => s.composite);
6296
- if (cs.length) xs.push(cs.reduce((a, b) => a + b, 0) / cs.length);
6297
- }
6298
- return xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0;
6536
+ return campaignMeanComposite(campaign);
6299
6537
  }
6300
6538
  function buildLoopProvenanceRecord(args) {
6301
6539
  if (!args.runId.trim() || !args.runDir.trim()) {
@@ -6305,6 +6543,7 @@ function buildLoopProvenanceRecord(args) {
6305
6543
  if (!Number.isFinite(timestampMs) || new Date(timestampMs).toISOString() !== args.timestamp) {
6306
6544
  throw new Error("buildLoopProvenanceRecord: timestamp must be a canonical ISO instant");
6307
6545
  }
6546
+ assertGateContributions(args.gate.contributingGates, "buildLoopProvenanceRecord");
6308
6547
  const agentReceipts = args.costReceipts.filter((receipt) => receipt.channel === "agent");
6309
6548
  const integrity = summarizeAgentReceiptIntegrity(agentReceipts);
6310
6549
  const models = [...new Set(agentReceipts.map((receipt) => receipt.model))].sort();
@@ -6401,6 +6640,9 @@ function buildLoopProvenanceRecord(args) {
6401
6640
  if (promotedHash) {
6402
6641
  const promoted = candidateByHash.get(promotedHash);
6403
6642
  incumbentSurfaceHash = promoted.surfaceHash;
6643
+ if (promoted.composite === null) {
6644
+ throw new Error("buildLoopProvenanceRecord: promoted candidate is missing a composite");
6645
+ }
6404
6646
  incumbentComposite = promoted.composite;
6405
6647
  }
6406
6648
  }
@@ -6410,8 +6652,6 @@ function buildLoopProvenanceRecord(args) {
6410
6652
  );
6411
6653
  }
6412
6654
  const holdoutDeferred = args.holdout === "deferred";
6413
- const baselineHoldoutComposite = meanHoldoutComposite(args.baselineOnHoldout);
6414
- const winnerHoldoutComposite = meanHoldoutComposite(args.winnerOnHoldout);
6415
6655
  if (args.baselineOnHoldout.splitDigest !== args.winnerOnHoldout.splitDigest) {
6416
6656
  throw new Error("buildLoopProvenanceRecord: baseline and winner use different holdout splits");
6417
6657
  }
@@ -6425,7 +6665,17 @@ function buildLoopProvenanceRecord(args) {
6425
6665
  "buildLoopProvenanceRecord: neutralized campaign uses a different holdout split"
6426
6666
  );
6427
6667
  }
6428
- const neutralizedComposite = args.neutralizedOnHoldout ? meanHoldoutComposite(args.neutralizedOnHoldout) : void 0;
6668
+ if (holdoutDeferred && args.neutralizedOnHoldout) {
6669
+ throw new Error(
6670
+ "buildLoopProvenanceRecord: a deferred holdout cannot include a neutralized measurement"
6671
+ );
6672
+ }
6673
+ const holdoutMeasurement = holdoutDeferred ? { kind: "deferred" } : {
6674
+ kind: "measured",
6675
+ baseline: meanHoldoutComposite(args.baselineOnHoldout),
6676
+ winner: meanHoldoutComposite(args.winnerOnHoldout),
6677
+ ...args.neutralizedOnHoldout ? { neutralized: meanHoldoutComposite(args.neutralizedOnHoldout) } : {}
6678
+ };
6429
6679
  const diff = surfaceContentHash(args.baselineSurface) === surfaceContentHash(args.winnerSurface) ? "" : renderSurfaceDiff(args.winnerSurface, args.baselineSurface);
6430
6680
  const recordWithoutDigest = {
6431
6681
  schema: "tangle.loop-provenance",
@@ -6445,12 +6695,12 @@ function buildLoopProvenanceRecord(args) {
6445
6695
  splitDigest: args.baselineOnHoldout.splitDigest,
6446
6696
  baselineCampaignDigest: campaignMeasurementDigest(args.baselineOnHoldout),
6447
6697
  winnerCampaignDigest: campaignMeasurementDigest(args.winnerOnHoldout),
6448
- ...args.neutralizedSurface && args.neutralizedOnHoldout && neutralizedComposite !== void 0 ? {
6698
+ ...args.neutralizedSurface && args.neutralizedOnHoldout && holdoutMeasurement.kind === "measured" && holdoutMeasurement.neutralized !== void 0 ? {
6449
6699
  neutralized: {
6450
6700
  contentHash: surfaceContentHash(args.neutralizedSurface),
6451
6701
  campaignDigest: campaignMeasurementDigest(args.neutralizedOnHoldout),
6452
- composite: neutralizedComposite,
6453
- lift: neutralizedComposite - baselineHoldoutComposite
6702
+ composite: holdoutMeasurement.neutralized,
6703
+ lift: holdoutMeasurement.neutralized - holdoutMeasurement.baseline
6454
6704
  }
6455
6705
  } : {}
6456
6706
  },
@@ -6465,16 +6715,16 @@ function buildLoopProvenanceRecord(args) {
6465
6715
  delta: args.gate.delta,
6466
6716
  contributingGates: args.gate.contributingGates.map((g) => ({
6467
6717
  name: g.name,
6468
- passed: g.passed,
6718
+ status: g.status,
6469
6719
  detail: durableGateDetail(g.detail)
6470
6720
  }))
6471
6721
  },
6472
6722
  // Deferred holdout records the MODE, never a fabricated 0-lift: the
6473
6723
  // held-out comparison intentionally did not run in this loop.
6474
- ...holdoutDeferred ? { holdout: "deferred" } : {
6475
- baselineHoldoutComposite,
6476
- winnerHoldoutComposite,
6477
- heldOutLift: winnerHoldoutComposite - baselineHoldoutComposite
6724
+ ...holdoutMeasurement.kind === "deferred" ? { holdout: "deferred" } : {
6725
+ baselineHoldoutComposite: holdoutMeasurement.baseline,
6726
+ winnerHoldoutComposite: holdoutMeasurement.winner,
6727
+ heldOutLift: holdoutMeasurement.winner - holdoutMeasurement.baseline
6478
6728
  },
6479
6729
  backend: {
6480
6730
  verdict: integrity.verdict,
@@ -6540,6 +6790,8 @@ function campaignMeasurementDigest(campaign) {
6540
6790
  durationMs: cell.durationMs,
6541
6791
  seed: cell.seed,
6542
6792
  cached: cell.cached,
6793
+ errorStage: cell.errorStage ?? null,
6794
+ errorJudge: cell.errorJudge ?? null,
6543
6795
  error: cell.error ?? null
6544
6796
  }))
6545
6797
  });
@@ -6552,6 +6804,7 @@ function verifyLoopProvenanceRecord(record) {
6552
6804
  if (recordDigest !== canonicalDigest(recordWithoutDigest)) {
6553
6805
  throw new Error("loop provenance record digest does not match its contents");
6554
6806
  }
6807
+ assertGateContributions(record.gate?.contributingGates, "loop provenance");
6555
6808
  return record;
6556
6809
  }
6557
6810
  function canonicalDigest(value) {
@@ -6583,10 +6836,32 @@ function durableGateDetail(detail) {
6583
6836
  });
6584
6837
  }
6585
6838
  }
6586
- function validateCandidateMeasurement(candidate, expectedParentHash, expectedParentComposite, promoted) {
6587
- if (!Number.isFinite(candidate.composite)) {
6588
- throw new Error("buildLoopProvenanceRecord: candidate composite must be finite");
6839
+ function assertGateContributions(value, source) {
6840
+ if (!Array.isArray(value)) {
6841
+ throw new Error(`${source}: gate contributingGates must be an array`);
6589
6842
  }
6843
+ const statuses = /* @__PURE__ */ new Set(["pass", "fail", "not_evaluated"]);
6844
+ for (const [index, contribution] of value.entries()) {
6845
+ if (!contribution || typeof contribution !== "object") {
6846
+ throw new Error(`${source}: gate contribution ${index} must be an object`);
6847
+ }
6848
+ const item = contribution;
6849
+ if (typeof item.name !== "string" || item.name.length === 0) {
6850
+ throw new Error(`${source}: gate contribution ${index} must have a non-empty name`);
6851
+ }
6852
+ if (!statuses.has(String(item.status))) {
6853
+ throw new Error(
6854
+ `${source}: gate contribution '${item.name}' must have status pass, fail, or not_evaluated`
6855
+ );
6856
+ }
6857
+ if ("passed" in item) {
6858
+ throw new Error(
6859
+ `${source}: gate contribution '${item.name}' uses obsolete passed; use status instead`
6860
+ );
6861
+ }
6862
+ }
6863
+ }
6864
+ function validateCandidateMeasurement(candidate, expectedParentHash, expectedParentComposite, promoted) {
6590
6865
  if (!candidate.parentSurfaceHash || !/^[a-f0-9]{16}$/.test(candidate.parentSurfaceHash)) {
6591
6866
  throw new Error(
6592
6867
  "buildLoopProvenanceRecord: parentSurfaceHash must be 16 lowercase hex characters"
@@ -6600,11 +6875,6 @@ function validateCandidateMeasurement(candidate, expectedParentHash, expectedPar
6600
6875
  "buildLoopProvenanceRecord: candidate parentComposite does not match the incumbent"
6601
6876
  );
6602
6877
  }
6603
- if (candidate.eligibleForPromotion === void 0 || !candidate.coverage) {
6604
- throw new Error(
6605
- "buildLoopProvenanceRecord: candidate measurement requires eligibility and coverage"
6606
- );
6607
- }
6608
6878
  if (candidate.observedDeltaFromParent !== void 0) {
6609
6879
  if (!Number.isFinite(candidate.observedDeltaFromParent)) {
6610
6880
  throw new Error("buildLoopProvenanceRecord: observedDeltaFromParent must be finite");
@@ -6632,12 +6902,15 @@ function validateCandidateMeasurement(candidate, expectedParentHash, expectedPar
6632
6902
  );
6633
6903
  }
6634
6904
  const complete = coverage.scorableCells === coverage.expectedCells && coverage.unscorableCells.length === 0;
6635
- if (candidate.eligibleForPromotion !== void 0 && candidate.eligibleForPromotion !== complete) {
6905
+ if (candidate.eligibleForPromotion !== complete) {
6636
6906
  throw new Error(
6637
6907
  "buildLoopProvenanceRecord: candidate eligibility contradicts its coverage receipt"
6638
6908
  );
6639
6909
  }
6640
6910
  if (complete) {
6911
+ if (candidate.composite === null || !Number.isFinite(candidate.composite)) {
6912
+ throw new Error("buildLoopProvenanceRecord: complete candidate composite must be finite");
6913
+ }
6641
6914
  if (candidate.observedDeltaFromParent === void 0) {
6642
6915
  throw new Error(
6643
6916
  "buildLoopProvenanceRecord: complete candidate is missing observedDeltaFromParent"
@@ -6647,24 +6920,34 @@ function validateCandidateMeasurement(candidate, expectedParentHash, expectedPar
6647
6920
  if (Math.abs(candidate.observedDeltaFromParent - recomputed) > 1e-12) {
6648
6921
  throw new Error("buildLoopProvenanceRecord: observed delta does not match measured scores");
6649
6922
  }
6650
- } else if (candidate.observedDeltaFromParent !== void 0) {
6651
- throw new Error("buildLoopProvenanceRecord: incomplete candidate cannot carry observed delta");
6923
+ } else {
6924
+ if (candidate.composite !== null && !Number.isFinite(candidate.composite)) {
6925
+ throw new Error("buildLoopProvenanceRecord: candidate composite must be finite or null");
6926
+ }
6927
+ if (candidate.observedDeltaFromParent !== void 0) {
6928
+ throw new Error("buildLoopProvenanceRecord: incomplete candidate cannot carry observed delta");
6929
+ }
6652
6930
  }
6653
6931
  if (promoted && (!complete || (candidate.observedDeltaFromParent ?? 0) <= 0)) {
6654
6932
  throw new Error("buildLoopProvenanceRecord: promoted candidate must improve the incumbent");
6655
6933
  }
6656
6934
  }
6657
- var DECISION_OK = ["ship"];
6658
6935
  function hashId(parts) {
6659
6936
  return createHash2("sha256").update(parts.join(":")).digest("hex");
6660
6937
  }
6661
- function gateStatus(decision) {
6662
- return DECISION_OK.includes(decision) ? { code: "OK" } : { code: "ERROR", message: `gate decision: ${decision}` };
6663
- }
6664
6938
  function loopProvenanceSpans(record, opts = {}) {
6665
6939
  const traceId = hashId(["trace", record.runId]).slice(0, 32);
6666
- const baseNano = (opts.baseTimeMs ?? (Date.parse(record.timestamp) || Date.now())) * 1e6;
6667
- const endNano = baseNano + Math.max(1, record.totalDurationMs) * 1e6;
6940
+ const baseTimeMs = opts.baseTimeMs ?? (Date.parse(record.timestamp) || Date.now());
6941
+ const durationMs = Math.max(1, record.totalDurationMs);
6942
+ if (!Number.isSafeInteger(baseTimeMs) || baseTimeMs < 0) {
6943
+ throw new RangeError("loop provenance baseTimeMs must be a non-negative safe integer");
6944
+ }
6945
+ if (!Number.isSafeInteger(durationMs)) {
6946
+ throw new RangeError("loop provenance duration must be a safe integer number of milliseconds");
6947
+ }
6948
+ const baseTime = BigInt(baseTimeMs);
6949
+ const baseNano = (baseTime * 1000000n).toString();
6950
+ const endNano = ((baseTime + BigInt(durationMs)) * 1000000n).toString();
6668
6951
  const spans = [];
6669
6952
  const rootSpanId = hashId(["root", record.runId]).slice(0, 16);
6670
6953
  const rootAttributes = {
@@ -6687,7 +6970,7 @@ function loopProvenanceSpans(record, opts = {}) {
6687
6970
  startTimeUnixNano: baseNano,
6688
6971
  endTimeUnixNano: endNano,
6689
6972
  attributes: rootAttributes,
6690
- status: gateStatus(record.gate.decision),
6973
+ status: { code: "OK" },
6691
6974
  "tangle.runId": record.runId
6692
6975
  });
6693
6976
  const byGen = /* @__PURE__ */ new Map();
@@ -6698,7 +6981,9 @@ function loopProvenanceSpans(record, opts = {}) {
6698
6981
  }
6699
6982
  for (const [generation, cands] of [...byGen.entries()].sort((a, b) => a[0] - b[0])) {
6700
6983
  const genSpanId = hashId(["gen", record.runId, String(generation)]).slice(0, 16);
6701
- const bestComposite = Math.max(...cands.map((candidate) => candidate.composite));
6984
+ const measuredComposites = cands.flatMap(
6985
+ (candidate) => candidate.composite === null ? [] : [candidate.composite]
6986
+ );
6702
6987
  spans.push({
6703
6988
  traceId,
6704
6989
  spanId: genSpanId,
@@ -6710,7 +6995,7 @@ function loopProvenanceSpans(record, opts = {}) {
6710
6995
  "tangle.runId": record.runId,
6711
6996
  "tangle.generation": generation,
6712
6997
  "tangle.populationSize": cands.length,
6713
- "tangle.bestComposite": bestComposite
6998
+ ...measuredComposites.length > 0 ? { "tangle.bestComposite": Math.max(...measuredComposites) } : {}
6714
6999
  },
6715
7000
  "tangle.runId": record.runId,
6716
7001
  "tangle.generation": generation
@@ -6728,13 +7013,13 @@ function loopProvenanceSpans(record, opts = {}) {
6728
7013
  "tangle.contentHash": c.contentHash,
6729
7014
  "tangle.parentSurfaceHash": c.parentSurfaceHash,
6730
7015
  "tangle.parentComposite": c.parentComposite,
6731
- "tangle.composite": c.composite,
6732
7016
  "tangle.eligibleForPromotion": c.eligibleForPromotion,
6733
7017
  "tangle.expectedCells": c.coverage.expectedCells,
6734
7018
  "tangle.scorableCells": c.coverage.scorableCells,
6735
7019
  "tangle.unscorableCells": c.coverage.unscorableCells.length,
6736
7020
  "tangle.promoted": c.promoted
6737
7021
  };
7022
+ if (c.composite !== null) attributes["tangle.composite"] = c.composite;
6738
7023
  if (c.observedDeltaFromParent !== void 0) {
6739
7024
  attributes["tangle.observedDeltaFromParent"] = c.observedDeltaFromParent;
6740
7025
  }
@@ -6777,7 +7062,7 @@ function loopProvenanceSpans(record, opts = {}) {
6777
7062
  startTimeUnixNano: endNano,
6778
7063
  endTimeUnixNano: endNano,
6779
7064
  attributes: gateAttributes,
6780
- status: gateStatus(record.gate.decision),
7065
+ status: { code: "OK" },
6781
7066
  "tangle.runId": record.runId
6782
7067
  });
6783
7068
  return spans;
@@ -6790,26 +7075,25 @@ function provenanceSpansPath(runDir) {
6790
7075
  }
6791
7076
  function snapshotFromHoldout(index, surfaceHash2, surface, campaign) {
6792
7077
  const cells = campaign.cells.map((cell) => {
6793
- const judgeScores = Object.values(cell.judgeScores);
6794
- const composite = judgeScores.length === 0 ? 0 : judgeScores.reduce((s, j) => s + j.composite, 0) / judgeScores.length;
7078
+ const execution = campaignCellExecutionEvidence(cell);
7079
+ const quality = projectCampaignCellQuality(cell);
6795
7080
  const score = {
6796
7081
  scenarioId: cell.scenarioId,
6797
7082
  rep: cell.rep,
6798
- compositeMean: composite,
6799
- dimensions: Object.fromEntries(
6800
- Object.entries(cell.judgeScores).map(([name, s]) => [name, s.dimensions])
6801
- )
7083
+ compositeMean: quality.score ?? null,
7084
+ dimensions: quality.judgeScores?.perJudge ?? {},
7085
+ terminalOutcome: execution.terminalOutcome,
7086
+ executionErrorCount: execution.executionErrorCount ?? null
6802
7087
  };
6803
7088
  if (cell.error) score.errorMessage = cell.error;
6804
7089
  return score;
6805
7090
  });
6806
- const compositeMean = cells.length === 0 ? 0 : cells.reduce((s, c) => s + c.compositeMean, 0) / cells.length;
6807
7091
  return {
6808
7092
  index,
6809
7093
  surfaceHash: surfaceHash2,
6810
7094
  surface,
6811
7095
  cells,
6812
- compositeMean,
7096
+ compositeMean: campaignMeanCompositeOrNull(campaign),
6813
7097
  costUsd: campaign.aggregates.totalCostUsd,
6814
7098
  durationMs: campaign.durationMs
6815
7099
  };
@@ -7346,4 +7630,4 @@ export {
7346
7630
  emitLoopProvenance,
7347
7631
  skillOptOptimizationMethod
7348
7632
  };
7349
- //# sourceMappingURL=chunk-BTACVO2W.js.map
7633
+ //# sourceMappingURL=chunk-65QETEKG.js.map