@tangle-network/agent-eval 0.119.0 → 0.120.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 (55) hide show
  1. package/CHANGELOG.md +25 -3
  2. package/README.md +0 -26
  3. package/dist/analyst/index.d.ts +30 -39
  4. package/dist/analyst/index.js +9 -5
  5. package/dist/analyst/index.js.map +1 -1
  6. package/dist/benchmarks/index.d.ts +11 -5
  7. package/dist/benchmarks/index.js +7 -7
  8. package/dist/campaign/index.d.ts +121 -66
  9. package/dist/campaign/index.js +26 -10
  10. package/dist/{chunk-RLCJQ6VZ.js → chunk-32BZXMSO.js} +385 -120
  11. package/dist/chunk-32BZXMSO.js.map +1 -0
  12. package/dist/{chunk-XJ7JVCHB.js → chunk-3A246TSA.js} +2 -2
  13. package/dist/{chunk-LMJ2TGWJ.js → chunk-JM2SKQMS.js} +2 -2
  14. package/dist/{chunk-F6YUH3L4.js → chunk-JN2FCO5W.js} +14 -168
  15. package/dist/chunk-JN2FCO5W.js.map +1 -0
  16. package/dist/{chunk-D7AEXSM5.js → chunk-PICTDURQ.js} +2 -2
  17. package/dist/{chunk-6QIM2EAP.js → chunk-PXD6ZFNY.js} +99 -1
  18. package/dist/chunk-PXD6ZFNY.js.map +1 -0
  19. package/dist/{chunk-GERDAIAL.js → chunk-QWMPPZ3X.js} +3 -3
  20. package/dist/{chunk-7A4LIMMY.js → chunk-S5TT5R3L.js} +229 -97
  21. package/dist/chunk-S5TT5R3L.js.map +1 -0
  22. package/dist/{chunk-ZYHJNKI3.js → chunk-ULOKLHIQ.js} +42 -5
  23. package/dist/chunk-ULOKLHIQ.js.map +1 -0
  24. package/dist/{chunk-FIUFRXP3.js → chunk-WW2A73HW.js} +49 -91
  25. package/dist/chunk-WW2A73HW.js.map +1 -0
  26. package/dist/{chunk-PAHNGS65.js → chunk-XDIRG3TO.js} +175 -11
  27. package/dist/chunk-XDIRG3TO.js.map +1 -0
  28. package/dist/contract/index.d.ts +76 -43
  29. package/dist/contract/index.js +596 -32
  30. package/dist/contract/index.js.map +1 -1
  31. package/dist/index.d.ts +92 -75
  32. package/dist/index.js +38 -35
  33. package/dist/index.js.map +1 -1
  34. package/dist/openapi.json +1 -1
  35. package/dist/rl.d.ts +11 -5
  36. package/dist/{run-campaign-OBXSN5WK.js → run-campaign-HNFPJET4.js} +2 -2
  37. package/dist/traces.d.ts +39 -25
  38. package/dist/traces.js +3 -5
  39. package/docs/trace-analysis.md +6 -2
  40. package/package.json +2 -7
  41. package/dist/chunk-6QIM2EAP.js.map +0 -1
  42. package/dist/chunk-7A4LIMMY.js.map +0 -1
  43. package/dist/chunk-F6YUH3L4.js.map +0 -1
  44. package/dist/chunk-FIUFRXP3.js.map +0 -1
  45. package/dist/chunk-PAHNGS65.js.map +0 -1
  46. package/dist/chunk-RLCJQ6VZ.js.map +0 -1
  47. package/dist/chunk-ZYHJNKI3.js.map +0 -1
  48. package/dist/primeintellect/index.d.ts +0 -311
  49. package/dist/primeintellect/index.js +0 -348
  50. package/dist/primeintellect/index.js.map +0 -1
  51. /package/dist/{chunk-XJ7JVCHB.js.map → chunk-3A246TSA.js.map} +0 -0
  52. /package/dist/{chunk-LMJ2TGWJ.js.map → chunk-JM2SKQMS.js.map} +0 -0
  53. /package/dist/{chunk-D7AEXSM5.js.map → chunk-PICTDURQ.js.map} +0 -0
  54. /package/dist/{chunk-GERDAIAL.js.map → chunk-QWMPPZ3X.js.map} +0 -0
  55. /package/dist/{run-campaign-OBXSN5WK.js.map → run-campaign-HNFPJET4.js.map} +0 -0
@@ -1,15 +1,19 @@
1
1
  import {
2
+ assertCampaignSplitIdentity,
3
+ campaignCoverage,
4
+ canonicalJson,
2
5
  contentHash,
3
6
  createRunCostLedger,
7
+ formatCoverageFailures,
4
8
  fsCampaignStorage,
5
9
  resolveRunDir,
6
10
  runCampaign,
7
- summarizeBackendIntegrity
8
- } from "./chunk-PAHNGS65.js";
11
+ summarizeAgentReceiptIntegrity
12
+ } from "./chunk-XDIRG3TO.js";
9
13
  import {
10
14
  clamp01,
11
15
  validatePolicyEditCandidateRecord
12
- } from "./chunk-7A4LIMMY.js";
16
+ } from "./chunk-S5TT5R3L.js";
13
17
  import {
14
18
  detectRewardHacking
15
19
  } from "./chunk-ARU2PZFM.js";
@@ -21,8 +25,11 @@ import {
21
25
  stripFencedJson
22
26
  } from "./chunk-NJC7U437.js";
23
27
  import {
28
+ mcnemar,
24
29
  pairedBootstrap,
25
- weightedComposite
30
+ pairedRiskDifference,
31
+ weightedComposite,
32
+ wilcoxonSignedRank
26
33
  } from "./chunk-PJQFMIOX.js";
27
34
  import {
28
35
  CostLedger
@@ -292,6 +299,155 @@ async function runJudgeChat(tc, input, judgeName, request) {
292
299
  return paid.value;
293
300
  }
294
301
 
302
+ // src/paired-arms.ts
303
+ function pairArms(rows, opts) {
304
+ const { baselineArm, treatmentArm } = opts;
305
+ if (baselineArm === treatmentArm) {
306
+ throw new ValidationError(
307
+ `pairArms: baselineArm and treatmentArm are both '${baselineArm}' \u2014 an arm cannot be compared to itself`
308
+ );
309
+ }
310
+ const byArm = /* @__PURE__ */ new Map();
311
+ const armsSeen = /* @__PURE__ */ new Set();
312
+ for (const row of rows) {
313
+ armsSeen.add(row.arm);
314
+ if (row.arm !== baselineArm && row.arm !== treatmentArm) continue;
315
+ const byKey = byArm.get(row.arm) ?? /* @__PURE__ */ new Map();
316
+ const group = byKey.get(row.pairKey) ?? [];
317
+ group.push(row);
318
+ byKey.set(row.pairKey, group);
319
+ byArm.set(row.arm, byKey);
320
+ }
321
+ for (const arm of [baselineArm, treatmentArm]) {
322
+ if (!byArm.has(arm)) {
323
+ const seen = [...armsSeen].sort().join(", ") || "<none>";
324
+ throw new ValidationError(`pairArms: no rows for arm '${arm}' (arms present: ${seen})`);
325
+ }
326
+ }
327
+ const baselineByKey = byArm.get(baselineArm);
328
+ const treatmentByKey = byArm.get(treatmentArm);
329
+ const allKeys = [.../* @__PURE__ */ new Set([...baselineByKey.keys(), ...treatmentByKey.keys()])].sort();
330
+ const pairs = [];
331
+ const unpairedBaseline = [];
332
+ const unpairedTreatment = [];
333
+ for (const pairKey of allKeys) {
334
+ const b = baselineByKey.get(pairKey) ?? [];
335
+ const t = treatmentByKey.get(pairKey) ?? [];
336
+ if (b.length <= 1 && t.length <= 1) {
337
+ if (b.length === 1 && t.length === 1) {
338
+ pairs.push({ pairKey, repIndex: 0, baseline: b[0], treatment: t[0] });
339
+ } else {
340
+ unpairedBaseline.push(...b);
341
+ unpairedTreatment.push(...t);
342
+ }
343
+ continue;
344
+ }
345
+ const bByRep = indexByRepKey(b, pairKey, baselineArm);
346
+ const tByRep = indexByRepKey(t, pairKey, treatmentArm);
347
+ const repKeys = [.../* @__PURE__ */ new Set([...bByRep.keys(), ...tByRep.keys()])].sort();
348
+ let repIndex = 0;
349
+ for (const repKey of repKeys) {
350
+ const baseline = bByRep.get(repKey);
351
+ const treatment = tByRep.get(repKey);
352
+ if (baseline !== void 0 && treatment !== void 0) {
353
+ pairs.push({ pairKey, repIndex: repIndex++, baseline, treatment });
354
+ } else if (baseline !== void 0) {
355
+ unpairedBaseline.push(baseline);
356
+ } else if (treatment !== void 0) {
357
+ unpairedTreatment.push(treatment);
358
+ }
359
+ }
360
+ }
361
+ return { pairs, unpairedBaseline, unpairedTreatment };
362
+ }
363
+ function indexByRepKey(group, pairKey, arm) {
364
+ const byRep = /* @__PURE__ */ new Map();
365
+ for (const row of group) {
366
+ if (row.repKey === void 0) {
367
+ throw new ValidationError(
368
+ `pairArms: pairKey '${pairKey}' has multiple reps in an arm, but a row in arm '${arm}' is missing repKey \u2014 multi-rep items require an explicit repKey on every row so reps pair by identity (pairing reps by outcome or by index would bias the paired statistics)`
369
+ );
370
+ }
371
+ if (byRep.has(row.repKey)) {
372
+ throw new ValidationError(
373
+ `pairArms: duplicate repKey '${row.repKey}' for pairKey '${pairKey}' in arm '${arm}' \u2014 (pairKey, repKey) must uniquely identify a rep within an arm`
374
+ );
375
+ }
376
+ byRep.set(row.repKey, row);
377
+ }
378
+ return byRep;
379
+ }
380
+ function comparePairedArms(rows, opts) {
381
+ const { pairs, unpairedBaseline, unpairedTreatment } = pairArms(rows, opts);
382
+ let correctness = null;
383
+ const baselinePass = [];
384
+ const treatmentPass = [];
385
+ for (const pair of pairs) {
386
+ if (pair.baseline.pass === void 0 || pair.treatment.pass === void 0) continue;
387
+ baselinePass.push(pair.baseline.pass ? 1 : 0);
388
+ treatmentPass.push(pair.treatment.pass ? 1 : 0);
389
+ }
390
+ if (baselinePass.length > 0) {
391
+ const mc = mcnemar(baselinePass, treatmentPass);
392
+ correctness = {
393
+ b10: mc.b,
394
+ b01: mc.c,
395
+ mcnemar: mc,
396
+ riskDifference: pairedRiskDifference(baselinePass, treatmentPass)
397
+ };
398
+ }
399
+ const metricNames = opts.metricNames ?? [
400
+ ...new Set(
401
+ pairs.flatMap((p) => [
402
+ ...Object.keys(p.baseline.metrics ?? {}),
403
+ ...Object.keys(p.treatment.metrics ?? {})
404
+ ])
405
+ )
406
+ ].sort();
407
+ const metricDeltas = metricNames.map((name) => {
408
+ const before = [];
409
+ const after = [];
410
+ let nMissing = 0;
411
+ for (const pair of pairs) {
412
+ const b = metricValue(pair.baseline, name);
413
+ const t = metricValue(pair.treatment, name);
414
+ if (b === void 0 || t === void 0) {
415
+ nMissing++;
416
+ continue;
417
+ }
418
+ before.push(b);
419
+ after.push(t);
420
+ }
421
+ const bootstrapCi = before.length === 0 ? null : pairedBootstrap(before, after, opts.bootstrap);
422
+ return {
423
+ name,
424
+ n: before.length,
425
+ nMissing,
426
+ medianDelta: bootstrapCi === null ? Number.NaN : bootstrapCi.median,
427
+ meanDelta: bootstrapCi === null ? Number.NaN : bootstrapCi.mean,
428
+ bootstrapCi,
429
+ wilcoxon: before.length === 0 ? null : wilcoxonSignedRank(before, after)
430
+ };
431
+ });
432
+ return {
433
+ nPairs: pairs.length,
434
+ nUnpairedBaseline: unpairedBaseline.length,
435
+ nUnpairedTreatment: unpairedTreatment.length,
436
+ correctness,
437
+ metricDeltas
438
+ };
439
+ }
440
+ function metricValue(row, name) {
441
+ const v = row.metrics?.[name];
442
+ if (v === void 0) return void 0;
443
+ if (!Number.isFinite(v)) {
444
+ throw new ValidationError(
445
+ `comparePairedArms: non-finite value for metric '${name}' on pairKey '${row.pairKey}' (arm '${row.arm}'): ${v}`
446
+ );
447
+ }
448
+ return v;
449
+ }
450
+
295
451
  // src/pareto.ts
296
452
  function dominates(a, b, objectives) {
297
453
  let strictlyBetter = false;
@@ -1679,7 +1835,7 @@ function defaultProductionGate(options) {
1679
1835
  detail: {
1680
1836
  candidateUsd: ctx.cost.candidate,
1681
1837
  baselineUsd: ctx.cost.baseline,
1682
- budgetUsd: options.budgetUsd
1838
+ ...options.budgetUsd === void 0 ? {} : { budgetUsd: options.budgetUsd }
1683
1839
  }
1684
1840
  });
1685
1841
  if (!budgetPass) {
@@ -2326,7 +2482,7 @@ function assertCodeSurfaceIdentity(surface) {
2326
2482
  function codeSurfaceIdentityMaterial(surface) {
2327
2483
  assertCodeSurfaceIdentity(surface);
2328
2484
  return JSON.stringify({
2329
- schema: "tangle.code-surface.v1",
2485
+ schema: "tangle.code-surface",
2330
2486
  baseCommit: surface.baseCommit,
2331
2487
  baseTree: surface.baseTree,
2332
2488
  candidateTree: surface.candidateTree,
@@ -2344,6 +2500,33 @@ function surfaceContentHash(surface) {
2344
2500
  function surfaceHash(surface) {
2345
2501
  return surfaceContentHash(surface).slice("sha256:".length, "sha256:".length + 16);
2346
2502
  }
2503
+ function renderSurfaceDiff(winnerSurface, baselineSurface) {
2504
+ if (typeof winnerSurface === "string" && typeof baselineSurface === "string") {
2505
+ return [
2506
+ "--- baseline",
2507
+ "+++ winner",
2508
+ ...baselineSurface.split("\n").map((line) => `- ${line}`),
2509
+ ...winnerSurface.split("\n").map((line) => `+ ${line}`)
2510
+ ].join("\n");
2511
+ }
2512
+ const describe = (surface) => {
2513
+ if (typeof surface === "string") return "(prompt surface)";
2514
+ assertCodeSurfaceIdentity(surface);
2515
+ return [
2516
+ `baseCommit=${surface.baseCommit}`,
2517
+ `baseTree=${surface.baseTree}`,
2518
+ `candidateCommit=${surface.candidateCommit}`,
2519
+ `candidateTree=${surface.candidateTree}`,
2520
+ `patch=${surface.patch.sha256}`,
2521
+ `patchBytes=${surface.patch.byteLength}`,
2522
+ ...surface.summary ? [surface.summary] : []
2523
+ ].join("\n");
2524
+ };
2525
+ return `--- baseline
2526
+ ${describe(baselineSurface)}
2527
+ +++ winner
2528
+ ${describe(winnerSurface)}`;
2529
+ }
2347
2530
 
2348
2531
  // src/campaign/proposers/gepa.ts
2349
2532
  var REFLECTION_SYSTEM = 'You are an expert prompt engineer performing GEPA-style reflective mutation. You are given a prompt surface, its top trials (preserve what works) and its bottom trials (the evidence to fix). For each proposal, reason in this order before writing the payload: (1) LOCALIZE \u2014 point to the exact span of the current surface responsible for a bottom-trial failure; (2) DIAGNOSE the root cause (a missing rule, an ambiguous instruction, an over-broad directive), not just the symptom; (3) propose the MINIMAL, GENERALIZABLE edit that fixes the whole failure class \u2014 state it as a rule the agent should follow, never a patch memorized to the shown trials (that is overfitting and will not transfer to the held-out set); (4) PRESERVE every instruction the top trials depend on \u2014 do not delete or weaken working guidance. Put this localize\u2192diagnose\u2192fix reasoning in each proposal\'s `rationale`. Output ONLY a JSON object of shape {"proposals":[{"label":string,"rationale":string,"payload":string}]} where each `payload` is the FULL improved surface text. No prose outside the JSON.';
@@ -2595,79 +2778,6 @@ function campaignBreakdown(campaign) {
2595
2778
  return { dimensions, scenarios };
2596
2779
  }
2597
2780
 
2598
- // src/campaign/coverage.ts
2599
- function campaignCoverage(cells, scenarios, reps, requireJudgeScore) {
2600
- const expectedCellIds = designedCellIds(scenarios, reps);
2601
- const cellsById = /* @__PURE__ */ new Map();
2602
- for (const cell of cells) {
2603
- const matches = cellsById.get(cell.cellId) ?? [];
2604
- matches.push(cell);
2605
- cellsById.set(cell.cellId, matches);
2606
- }
2607
- const scorableCellIds = [];
2608
- const unscorableCells = [];
2609
- for (const cellId of expectedCellIds) {
2610
- const matches = cellsById.get(cellId) ?? [];
2611
- if (matches.length === 0) {
2612
- unscorableCells.push({ cellId, reason: "missing campaign cell" });
2613
- continue;
2614
- }
2615
- if (matches.length > 1) {
2616
- unscorableCells.push({ cellId, reason: `duplicate campaign cell (${matches.length})` });
2617
- continue;
2618
- }
2619
- const cell = matches[0];
2620
- const scoreEntries = Object.entries(cell.judgeScores);
2621
- const successfulScores = scoreEntries.map(([, score]) => score).filter((score) => score.failed !== true && Number.isFinite(score.composite));
2622
- const nonFiniteScores = scoreEntries.filter(
2623
- ([, score]) => score.failed !== true && (!Number.isFinite(score.composite) || Object.values(score.dimensions).some((value) => !Number.isFinite(value)))
2624
- );
2625
- const reasons = [];
2626
- if (cell.error) reasons.push(cell.error);
2627
- if (cell.artifact === null || cell.artifact === void 0) reasons.push("missing artifact");
2628
- if (!cell.error && requireJudgeScore && successfulScores.length === 0) {
2629
- reasons.push("no successful finite judge score");
2630
- }
2631
- if (scoreEntries.some(([, score]) => score.failed === true)) {
2632
- reasons.push("judge score marked failed");
2633
- }
2634
- if (nonFiniteScores.length > 0) {
2635
- reasons.push(
2636
- `non-finite judge score: ${nonFiniteScores.map(([name]) => name).sort().join(", ")}`
2637
- );
2638
- }
2639
- if (reasons.length > 0) {
2640
- unscorableCells.push({ cellId, reason: reasons.join("; ") });
2641
- } else {
2642
- scorableCellIds.push(cellId);
2643
- }
2644
- }
2645
- const expected = new Set(expectedCellIds);
2646
- for (const cell of cells) {
2647
- if (!expected.has(cell.cellId)) {
2648
- unscorableCells.push({ cellId: cell.cellId, reason: "unexpected campaign cell" });
2649
- }
2650
- }
2651
- return {
2652
- complete: unscorableCells.length === 0 && scorableCellIds.length === expectedCellIds.length,
2653
- expectedCellIds,
2654
- scorableCellIds,
2655
- unscorableCells
2656
- };
2657
- }
2658
- function formatCoverageFailures(coverage) {
2659
- const shown = coverage.unscorableCells.slice(0, 3).map((cell) => `${cell.cellId}: ${cell.reason}`).join("; ");
2660
- const remainder = coverage.unscorableCells.length - Math.min(3, coverage.unscorableCells.length);
2661
- return remainder > 0 ? `${shown}; +${remainder} more` : shown || "unknown coverage failure";
2662
- }
2663
- function designedCellIds(scenarios, reps) {
2664
- const ids = [];
2665
- for (const scenario of scenarios) {
2666
- for (let rep = 0; rep < reps; rep++) ids.push(`${scenario.id}:${rep}`);
2667
- }
2668
- return ids;
2669
- }
2670
-
2671
2781
  // src/campaign/presets/run-optimization.ts
2672
2782
  async function runOptimization(opts) {
2673
2783
  const { proposer } = opts;
@@ -2760,6 +2870,7 @@ async function runOptimization(opts) {
2760
2870
  costLedger,
2761
2871
  costPhase: "search.proposal"
2762
2872
  });
2873
+ if (proposed.length === 0) break;
2763
2874
  const candidates = proposed.map(
2764
2875
  (p) => isProposedCandidate(p) ? p : { surface: p, label: "", rationale: "" }
2765
2876
  );
@@ -2937,7 +3048,7 @@ var DEFAULT_DISPATCH_TIMEOUT_MS = 6e5;
2937
3048
  async function runImprovementLoop(opts) {
2938
3049
  if (opts.autoOnPromote === "config") {
2939
3050
  throw new Error(
2940
- "runImprovementLoop: autoOnPromote='config' is deferred to Pass B (requires shadow deploy + rollback + ensemble judges). Use 'pr' or 'none' in v0.40."
3051
+ "runImprovementLoop: autoOnPromote='config' requires isolated deployment, rollback, and independent validation. Use 'pr' or 'none'."
2941
3052
  );
2942
3053
  }
2943
3054
  if (opts.tracing === "off" && opts.proposer) {
@@ -2970,7 +3081,7 @@ async function runImprovementLoop(opts) {
2970
3081
  const dispatchTimeoutMs = opts.dispatchTimeoutMs ?? DEFAULT_DISPATCH_TIMEOUT_MS;
2971
3082
  const optimization = await runOptimization({ ...opts, dispatchTimeoutMs, costLedger });
2972
3083
  const winnerIsBaseline = optimization.winnerSurfaceHash === surfaceHash(opts.baselineSurface);
2973
- const { runCampaign: runCampaign2 } = await import("./run-campaign-OBXSN5WK.js");
3084
+ const { runCampaign: runCampaign2 } = await import("./run-campaign-HNFPJET4.js");
2974
3085
  const baselineOnHoldout = await runCampaign2({
2975
3086
  ...opts,
2976
3087
  costLedger,
@@ -3020,15 +3131,18 @@ async function runImprovementLoop(opts) {
3020
3131
  }
3021
3132
  let neutralizedArtifacts;
3022
3133
  let neutralizedJudgeScores;
3134
+ let neutralizedOnHoldout;
3135
+ let neutralizedSurface;
3023
3136
  if (opts.neutralize && !winnerIsBaseline) {
3024
- const neutralizedSurface = opts.neutralize(optimization.winnerSurface, opts.baselineSurface);
3025
- const neutralizedOnHoldout = await runCampaign2({
3137
+ const surface = opts.neutralize(optimization.winnerSurface, opts.baselineSurface);
3138
+ neutralizedSurface = surface;
3139
+ neutralizedOnHoldout = await runCampaign2({
3026
3140
  ...opts,
3027
3141
  costLedger,
3028
3142
  costPhase: "holdout.neutralized",
3029
3143
  dispatchTimeoutMs,
3030
3144
  scenarios: opts.holdoutScenarios,
3031
- dispatch: (scenario, ctx) => opts.dispatchWithSurface(neutralizedSurface, scenario, ctx),
3145
+ dispatch: (scenario, ctx) => opts.dispatchWithSurface(surface, scenario, ctx),
3032
3146
  runDir: `${opts.runDir}/holdout-neutralized`
3033
3147
  });
3034
3148
  assertCompleteHoldout("neutralized", neutralizedOnHoldout);
@@ -3064,8 +3178,7 @@ async function runImprovementLoop(opts) {
3064
3178
  costPhase: "promotion.gate",
3065
3179
  signal: new AbortController().signal
3066
3180
  });
3067
- const render = opts.renderPromotedDiff ?? defaultRenderDiff;
3068
- const promotedDiff = optimization.winnerSurfaceHash === surfaceHash(opts.baselineSurface) ? "" : render(optimization.winnerSurface, opts.baselineSurface);
3181
+ const promotedDiff = optimization.winnerSurfaceHash === surfaceHash(opts.baselineSurface) ? "" : renderSurfaceDiff(optimization.winnerSurface, opts.baselineSurface);
3069
3182
  let prResult;
3070
3183
  if (opts.autoOnPromote === "pr" && gateResult.decision === "ship") {
3071
3184
  prResult = openAutoPr({
@@ -3080,28 +3193,13 @@ async function runImprovementLoop(opts) {
3080
3193
  ...optimization,
3081
3194
  baselineOnHoldout,
3082
3195
  winnerOnHoldout,
3196
+ ...neutralizedOnHoldout && neutralizedSurface ? { neutralizedOnHoldout, neutralizedSurface } : {},
3083
3197
  gateResult,
3084
3198
  promotedDiff,
3085
3199
  prResult,
3086
3200
  cost: costLedger.summary()
3087
3201
  };
3088
3202
  }
3089
- function defaultRenderDiff(winnerSurface, baselineSurface) {
3090
- if (typeof winnerSurface !== "string" || typeof baselineSurface !== "string") {
3091
- const fmt = (s) => typeof s === "string" ? "(prompt surface)" : `worktree=${s.worktreeRef}${s.baseRef ? ` base=${s.baseRef}` : ""}${s.summary ? `
3092
- ${s.summary}` : ""}`;
3093
- return `--- baseline
3094
- ${fmt(baselineSurface)}
3095
- +++ winner
3096
- ${fmt(winnerSurface)}`;
3097
- }
3098
- const lines = [];
3099
- lines.push("--- baseline");
3100
- lines.push("+++ winner");
3101
- for (const l of baselineSurface.split("\n")) lines.push(`- ${l}`);
3102
- for (const l of winnerSurface.split("\n")) lines.push(`+ ${l}`);
3103
- return lines.join("\n");
3104
- }
3105
3203
 
3106
3204
  // src/campaign/presets/run-eval.ts
3107
3205
  async function runEval(opts) {
@@ -3126,6 +3224,39 @@ function evolutionaryProposer(opts) {
3126
3224
  // src/campaign/provenance.ts
3127
3225
  import { createHash as createHash2 } from "crypto";
3128
3226
  import { join as join2 } from "path";
3227
+ function loopProvenanceArgsFromResult(input) {
3228
+ const { result } = input;
3229
+ return {
3230
+ runId: input.runId,
3231
+ runDir: input.runDir,
3232
+ timestamp: input.timestamp,
3233
+ baselineSurface: input.baselineSurface,
3234
+ winnerSurface: result.winnerSurface,
3235
+ ...result.winnerLabel ? { winnerLabel: result.winnerLabel } : {},
3236
+ ...result.winnerRationale ? { winnerRationale: result.winnerRationale } : {},
3237
+ baselineSearchCampaign: result.baselineCampaign,
3238
+ generations: result.generations.map(({ record, surfaces }) => ({
3239
+ generationIndex: record.generationIndex,
3240
+ candidates: record.candidates,
3241
+ promoted: record.promoted,
3242
+ surfaces: surfaces.map(({ surfaceHash: surfaceHash2, surface, campaign }) => ({
3243
+ surfaceHash: surfaceHash2,
3244
+ surface,
3245
+ campaign
3246
+ }))
3247
+ })),
3248
+ gate: result.gateResult,
3249
+ baselineOnHoldout: result.baselineOnHoldout,
3250
+ winnerOnHoldout: result.winnerOnHoldout,
3251
+ ...result.neutralizedSurface && result.neutralizedOnHoldout ? {
3252
+ neutralizedSurface: result.neutralizedSurface,
3253
+ neutralizedOnHoldout: result.neutralizedOnHoldout
3254
+ } : {},
3255
+ costReceipts: input.costReceipts,
3256
+ totalCostUsd: input.totalCostUsd,
3257
+ totalDurationMs: input.totalDurationMs
3258
+ };
3259
+ }
3129
3260
  function meanHoldoutComposite(campaign) {
3130
3261
  const xs = [];
3131
3262
  for (const cell of campaign.cells) {
@@ -3136,29 +3267,41 @@ function meanHoldoutComposite(campaign) {
3136
3267
  return xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : 0;
3137
3268
  }
3138
3269
  function buildLoopProvenanceRecord(args) {
3139
- const integrity = summarizeBackendIntegrity(args.workerRecords);
3140
- const models = [...new Set(args.workerRecords.map((r) => r.model))].sort();
3141
- if (!Number.isFinite(args.baselineSearchComposite)) {
3270
+ if (!args.runId.trim() || !args.runDir.trim()) {
3271
+ throw new Error("buildLoopProvenanceRecord: runId and runDir must be non-empty");
3272
+ }
3273
+ const timestampMs = Date.parse(args.timestamp);
3274
+ if (!Number.isFinite(timestampMs) || new Date(timestampMs).toISOString() !== args.timestamp) {
3275
+ throw new Error("buildLoopProvenanceRecord: timestamp must be a canonical ISO instant");
3276
+ }
3277
+ const agentReceipts = args.costReceipts.filter((receipt) => receipt.channel === "agent");
3278
+ const integrity = summarizeAgentReceiptIntegrity(agentReceipts);
3279
+ const models = [...new Set(agentReceipts.map((receipt) => receipt.model))].sort();
3280
+ const baselineSearchComposite = campaignMeanComposite(args.baselineSearchCampaign);
3281
+ if (!Number.isFinite(baselineSearchComposite)) {
3142
3282
  throw new Error("buildLoopProvenanceRecord: baselineSearchComposite must be finite");
3143
3283
  }
3144
3284
  const candidates = [];
3145
3285
  let incumbentSurfaceHash = surfaceHash(args.baselineSurface);
3146
- let incumbentComposite = args.baselineSearchComposite;
3286
+ let incumbentComposite = baselineSearchComposite;
3147
3287
  let previousGeneration = -1;
3148
3288
  for (const gen of args.generations) {
3149
- if (!Number.isSafeInteger(gen.generationIndex) || gen.generationIndex <= previousGeneration) {
3289
+ if (!Number.isSafeInteger(gen.generationIndex) || gen.generationIndex !== previousGeneration + 1) {
3150
3290
  throw new Error(
3151
- "buildLoopProvenanceRecord: generation indices must be strictly increasing integers"
3291
+ "buildLoopProvenanceRecord: generation indices must be contiguous integers starting at zero"
3152
3292
  );
3153
3293
  }
3154
3294
  previousGeneration = gen.generationIndex;
3295
+ if (gen.candidates.length === 0) {
3296
+ throw new Error("buildLoopProvenanceRecord: a recorded generation must contain a candidate");
3297
+ }
3155
3298
  if (new Set(gen.promoted).size !== gen.promoted.length || gen.promoted.length > 1) {
3156
3299
  throw new Error(
3157
3300
  "buildLoopProvenanceRecord: each generation may promote at most one candidate"
3158
3301
  );
3159
3302
  }
3160
3303
  const promotedSet = new Set(gen.promoted);
3161
- const surfaceByHash = new Map(gen.surfaces.map((s) => [s.surfaceHash, s.surface]));
3304
+ const surfaceByHash = new Map(gen.surfaces.map((measured) => [measured.surfaceHash, measured]));
3162
3305
  const candidateByHash = new Map(
3163
3306
  gen.candidates.map((candidate) => [candidate.surfaceHash, candidate])
3164
3307
  );
@@ -3185,19 +3328,26 @@ function buildLoopProvenanceRecord(args) {
3185
3328
  incumbentComposite,
3186
3329
  promotedSet.has(c.surfaceHash)
3187
3330
  );
3188
- const surface = surfaceByHash.get(c.surfaceHash);
3189
- if (surface === void 0) {
3331
+ const measured = surfaceByHash.get(c.surfaceHash);
3332
+ if (measured === void 0) {
3190
3333
  throw new Error("buildLoopProvenanceRecord: measured candidate is missing its surface");
3191
3334
  }
3335
+ const { surface, campaign } = measured;
3192
3336
  if (surfaceHash(surface) !== c.surfaceHash) {
3193
3337
  throw new Error(
3194
3338
  "buildLoopProvenanceRecord: candidate surface hash does not match its surface bytes"
3195
3339
  );
3196
3340
  }
3341
+ if (campaign.splitDigest !== args.baselineSearchCampaign.splitDigest) {
3342
+ throw new Error(
3343
+ "buildLoopProvenanceRecord: candidate campaign does not match the search split"
3344
+ );
3345
+ }
3197
3346
  const entry = {
3198
3347
  generation: gen.generationIndex,
3199
3348
  surfaceHash: c.surfaceHash,
3200
3349
  contentHash: surfaceContentHash(surface),
3350
+ campaignDigest: campaignMeasurementDigest(campaign),
3201
3351
  parentSurfaceHash: c.parentSurfaceHash,
3202
3352
  parentComposite: c.parentComposite,
3203
3353
  eligibleForPromotion: c.eligibleForPromotion,
@@ -3233,23 +3383,61 @@ function buildLoopProvenanceRecord(args) {
3233
3383
  }
3234
3384
  const baselineHoldoutComposite = meanHoldoutComposite(args.baselineOnHoldout);
3235
3385
  const winnerHoldoutComposite = meanHoldoutComposite(args.winnerOnHoldout);
3236
- const record = {
3237
- schema: "tangle.loop-provenance.v3",
3386
+ if (args.baselineOnHoldout.splitDigest !== args.winnerOnHoldout.splitDigest) {
3387
+ throw new Error("buildLoopProvenanceRecord: baseline and winner use different holdout splits");
3388
+ }
3389
+ if (args.neutralizedSurface === void 0 !== (args.neutralizedOnHoldout === void 0)) {
3390
+ throw new Error(
3391
+ "buildLoopProvenanceRecord: neutralized surface and campaign must be supplied together"
3392
+ );
3393
+ }
3394
+ if (args.neutralizedOnHoldout && args.neutralizedOnHoldout.splitDigest !== args.baselineOnHoldout.splitDigest) {
3395
+ throw new Error(
3396
+ "buildLoopProvenanceRecord: neutralized campaign uses a different holdout split"
3397
+ );
3398
+ }
3399
+ const neutralizedComposite = args.neutralizedOnHoldout ? meanHoldoutComposite(args.neutralizedOnHoldout) : void 0;
3400
+ const diff = surfaceContentHash(args.baselineSurface) === surfaceContentHash(args.winnerSurface) ? "" : renderSurfaceDiff(args.winnerSurface, args.baselineSurface);
3401
+ const recordWithoutDigest = {
3402
+ schema: "tangle.loop-provenance",
3238
3403
  runId: args.runId,
3239
3404
  runDir: args.runDir,
3240
3405
  timestamp: args.timestamp,
3241
3406
  baselineContentHash: surfaceContentHash(args.baselineSurface),
3242
3407
  winnerContentHash: surfaceContentHash(args.winnerSurface),
3243
- diff: args.diff,
3408
+ diff,
3244
3409
  candidates,
3245
- baselineSearchComposite: args.baselineSearchComposite,
3410
+ evidence: {
3411
+ search: {
3412
+ splitDigest: args.baselineSearchCampaign.splitDigest,
3413
+ baselineCampaignDigest: campaignMeasurementDigest(args.baselineSearchCampaign)
3414
+ },
3415
+ holdout: {
3416
+ splitDigest: args.baselineOnHoldout.splitDigest,
3417
+ baselineCampaignDigest: campaignMeasurementDigest(args.baselineOnHoldout),
3418
+ winnerCampaignDigest: campaignMeasurementDigest(args.winnerOnHoldout),
3419
+ ...args.neutralizedSurface && args.neutralizedOnHoldout && neutralizedComposite !== void 0 ? {
3420
+ neutralized: {
3421
+ contentHash: surfaceContentHash(args.neutralizedSurface),
3422
+ campaignDigest: campaignMeasurementDigest(args.neutralizedOnHoldout),
3423
+ composite: neutralizedComposite,
3424
+ lift: neutralizedComposite - baselineHoldoutComposite
3425
+ }
3426
+ } : {}
3427
+ },
3428
+ costReceiptsDigest: canonicalDigest(
3429
+ [...args.costReceipts].sort((left, right) => left.callId.localeCompare(right.callId))
3430
+ )
3431
+ },
3432
+ baselineSearchComposite,
3246
3433
  gate: {
3247
3434
  decision: args.gate.decision,
3248
3435
  reasons: args.gate.reasons,
3249
3436
  delta: args.gate.delta,
3250
3437
  contributingGates: args.gate.contributingGates.map((g) => ({
3251
3438
  name: g.name,
3252
- passed: g.passed
3439
+ passed: g.passed,
3440
+ detail: durableGateDetail(g.detail)
3253
3441
  }))
3254
3442
  },
3255
3443
  baselineHoldoutComposite,
@@ -3266,10 +3454,81 @@ function buildLoopProvenanceRecord(args) {
3266
3454
  totalCostUsd: args.totalCostUsd,
3267
3455
  totalDurationMs: args.totalDurationMs
3268
3456
  };
3269
- if (args.winnerLabel) record.winnerLabel = args.winnerLabel;
3270
- if (args.winnerRationale) record.winnerRationale = args.winnerRationale;
3457
+ if (args.winnerLabel) recordWithoutDigest.winnerLabel = args.winnerLabel;
3458
+ if (args.winnerRationale) recordWithoutDigest.winnerRationale = args.winnerRationale;
3459
+ return {
3460
+ ...recordWithoutDigest,
3461
+ recordDigest: canonicalDigest(recordWithoutDigest)
3462
+ };
3463
+ }
3464
+ function campaignMeasurementDigest(campaign) {
3465
+ assertCampaignSplitIdentity(campaign.scenarios, campaign.reps, campaign.splitDigest);
3466
+ return canonicalDigest({
3467
+ schema: "tangle.campaign-measurement",
3468
+ manifestHash: campaign.manifestHash,
3469
+ splitDigest: campaign.splitDigest,
3470
+ seed: campaign.seed,
3471
+ reps: campaign.reps,
3472
+ runDir: campaign.runDir,
3473
+ scenarios: campaign.scenarios,
3474
+ cells: [...campaign.cells].sort((left, right) => left.cellId.localeCompare(right.cellId)).map((cell) => ({
3475
+ manifestHash: cell.manifestHash ?? null,
3476
+ cellId: cell.cellId,
3477
+ scenarioId: cell.scenarioId,
3478
+ rep: cell.rep,
3479
+ generation: cell.generation ?? null,
3480
+ judgeScores: cell.judgeScores,
3481
+ costUsd: cell.costUsd,
3482
+ costEstimated: cell.costEstimated ?? null,
3483
+ costCallIds: [...cell.costCallIds ?? []].sort(),
3484
+ tokenUsage: cell.tokenUsage,
3485
+ resolvedModel: cell.resolvedModel ?? null,
3486
+ durationMs: cell.durationMs,
3487
+ seed: cell.seed,
3488
+ cached: cell.cached,
3489
+ error: cell.error ?? null
3490
+ }))
3491
+ });
3492
+ }
3493
+ function verifyLoopProvenanceRecord(record) {
3494
+ if (record.schema !== "tangle.loop-provenance") {
3495
+ throw new Error("loop provenance has an unsupported schema");
3496
+ }
3497
+ const { recordDigest, ...recordWithoutDigest } = record;
3498
+ if (recordDigest !== canonicalDigest(recordWithoutDigest)) {
3499
+ throw new Error("loop provenance record digest does not match its contents");
3500
+ }
3271
3501
  return record;
3272
3502
  }
3503
+ function canonicalDigest(value) {
3504
+ const json = JSON.stringify(value, function strictJson(_key, item) {
3505
+ if (typeof item === "number" && !Number.isFinite(item)) {
3506
+ throw new Error("canonical digest cannot encode a non-finite number");
3507
+ }
3508
+ if (typeof item === "bigint" || typeof item === "function" || typeof item === "symbol") {
3509
+ throw new Error(`canonical digest cannot encode ${typeof item}`);
3510
+ }
3511
+ if (item === void 0 && Array.isArray(this)) {
3512
+ throw new Error("canonical digest cannot encode an undefined array item");
3513
+ }
3514
+ if (item instanceof Map || item instanceof Set) {
3515
+ throw new Error(`canonical digest cannot encode ${item instanceof Map ? "Map" : "Set"}`);
3516
+ }
3517
+ return item;
3518
+ });
3519
+ if (json === void 0) throw new Error("canonical digest value is not serializable");
3520
+ return `sha256:${contentHash(JSON.parse(json))}`;
3521
+ }
3522
+ function durableGateDetail(detail) {
3523
+ if (detail === void 0) return null;
3524
+ try {
3525
+ return JSON.parse(canonicalJson(detail));
3526
+ } catch (cause) {
3527
+ throw new Error("buildLoopProvenanceRecord: gate detail must be canonical JSON", {
3528
+ cause
3529
+ });
3530
+ }
3531
+ }
3273
3532
  function validateCandidateMeasurement(candidate, expectedParentHash, expectedParentComposite, promoted) {
3274
3533
  if (!Number.isFinite(candidate.composite)) {
3275
3534
  throw new Error("buildLoopProvenanceRecord: candidate composite must be finite");
@@ -3552,6 +3811,8 @@ export {
3552
3811
  adversarialJudge,
3553
3812
  createCustomJudge,
3554
3813
  defaultJudges,
3814
+ pairArms,
3815
+ comparePairedArms,
3555
3816
  recoverTruncatedJson,
3556
3817
  dominates,
3557
3818
  paretoFrontier,
@@ -3594,6 +3855,7 @@ export {
3594
3855
  codeSurfaceIdentityMaterial,
3595
3856
  surfaceContentHash,
3596
3857
  surfaceHash,
3858
+ renderSurfaceDiff,
3597
3859
  gepaProposer,
3598
3860
  extractH2Sections,
3599
3861
  countSentenceEdits,
@@ -3601,13 +3863,16 @@ export {
3601
3863
  campaignBreakdown,
3602
3864
  runOptimization,
3603
3865
  runImprovementLoop,
3604
- defaultRenderDiff,
3605
3866
  runEval,
3606
3867
  evolutionaryProposer,
3868
+ loopProvenanceArgsFromResult,
3607
3869
  buildLoopProvenanceRecord,
3870
+ campaignMeasurementDigest,
3871
+ verifyLoopProvenanceRecord,
3872
+ canonicalDigest,
3608
3873
  loopProvenanceSpans,
3609
3874
  provenanceRecordPath,
3610
3875
  provenanceSpansPath,
3611
3876
  emitLoopProvenance
3612
3877
  };
3613
- //# sourceMappingURL=chunk-RLCJQ6VZ.js.map
3878
+ //# sourceMappingURL=chunk-32BZXMSO.js.map