@tangle-network/agent-eval 0.119.1 → 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 (33) hide show
  1. package/CHANGELOG.md +19 -3
  2. package/README.md +0 -26
  3. package/dist/benchmarks/index.d.ts +11 -5
  4. package/dist/benchmarks/index.js +4 -4
  5. package/dist/campaign/index.d.ts +93 -37
  6. package/dist/campaign/index.js +23 -7
  7. package/dist/{chunk-PSXJ32SR.js → chunk-32BZXMSO.js} +229 -118
  8. package/dist/chunk-32BZXMSO.js.map +1 -0
  9. package/dist/{chunk-XJ7JVCHB.js → chunk-3A246TSA.js} +2 -2
  10. package/dist/{chunk-XMBOU5W7.js → chunk-JN2FCO5W.js} +4 -5
  11. package/dist/chunk-JN2FCO5W.js.map +1 -0
  12. package/dist/{chunk-D7AEXSM5.js → chunk-PICTDURQ.js} +2 -2
  13. package/dist/{chunk-PAHNGS65.js → chunk-XDIRG3TO.js} +175 -11
  14. package/dist/chunk-XDIRG3TO.js.map +1 -0
  15. package/dist/contract/index.d.ts +52 -28
  16. package/dist/contract/index.js +350 -62
  17. package/dist/contract/index.js.map +1 -1
  18. package/dist/index.d.ts +22 -8
  19. package/dist/index.js +9 -5
  20. package/dist/index.js.map +1 -1
  21. package/dist/openapi.json +1 -1
  22. package/dist/rl.d.ts +11 -5
  23. package/dist/{run-campaign-OBXSN5WK.js → run-campaign-HNFPJET4.js} +2 -2
  24. package/package.json +1 -6
  25. package/dist/chunk-PAHNGS65.js.map +0 -1
  26. package/dist/chunk-PSXJ32SR.js.map +0 -1
  27. package/dist/chunk-XMBOU5W7.js.map +0 -1
  28. package/dist/primeintellect/index.d.ts +0 -311
  29. package/dist/primeintellect/index.js +0 -348
  30. package/dist/primeintellect/index.js.map +0 -1
  31. /package/dist/{chunk-XJ7JVCHB.js.map → chunk-3A246TSA.js.map} +0 -0
  32. /package/dist/{chunk-D7AEXSM5.js.map → chunk-PICTDURQ.js.map} +0 -0
  33. /package/dist/{run-campaign-OBXSN5WK.js.map → run-campaign-HNFPJET4.js.map} +0 -0
@@ -13,12 +13,15 @@ import {
13
13
  import {
14
14
  analyzeRuns,
15
15
  summarizeExecution
16
- } from "../chunk-XJ7JVCHB.js";
16
+ } from "../chunk-3A246TSA.js";
17
17
  import {
18
18
  REFERENCE_EQUIVALENCE_INPUT_LIMITS,
19
19
  REFERENCE_EQUIVALENCE_JUDGE_VERSION,
20
20
  buildEvidenceVector,
21
+ buildLoopProvenanceRecord,
22
+ campaignBreakdown,
21
23
  campaignMeanComposite,
24
+ canonicalDigest,
22
25
  composeGate,
23
26
  createReferenceEquivalenceJudge,
24
27
  defaultProductionGate,
@@ -26,24 +29,30 @@ import {
26
29
  evolutionaryProposer,
27
30
  gepaProposer,
28
31
  heldOutGate,
32
+ loopProvenanceArgsFromResult,
29
33
  pairArms,
30
34
  paretoPolicy,
31
35
  paretoSignificanceGate,
32
36
  powerPreflight,
37
+ renderSurfaceDiff,
33
38
  runEval,
34
39
  runImprovementLoop,
35
40
  runReferenceEquivalenceJudge,
36
41
  surfaceContentHash,
37
- surfaceHash
38
- } from "../chunk-PSXJ32SR.js";
42
+ surfaceHash,
43
+ verifyLoopProvenanceRecord
44
+ } from "../chunk-32BZXMSO.js";
39
45
  import {
40
- canonicalJson,
46
+ assertCampaignSplitIdentity,
47
+ assertRealAgentReceipts,
48
+ campaignCoverage,
49
+ campaignSplitDigest,
41
50
  createRunCostLedger,
42
51
  fsCampaignStorage,
43
52
  inMemoryCampaignStorage,
44
53
  resolveRunDir,
45
54
  runCampaign
46
- } from "../chunk-PAHNGS65.js";
55
+ } from "../chunk-XDIRG3TO.js";
47
56
  import {
48
57
  buildDefaultAnalystRegistry,
49
58
  createChatClient
@@ -60,7 +69,9 @@ import "../chunk-DPZAEKA6.js";
60
69
  import {
61
70
  pairedBootstrap
62
71
  } from "../chunk-PJQFMIOX.js";
63
- import "../chunk-JHOJHHU7.js";
72
+ import {
73
+ CostLedger
74
+ } from "../chunk-JHOJHHU7.js";
64
75
  import "../chunk-VI2UW6B6.js";
65
76
  import {
66
77
  recordAggregateMeasurements,
@@ -253,29 +264,17 @@ async function runSelfImprove(opts, costLedger, startedAt, runDir, storage) {
253
264
  candidateCandidateId: "winner"
254
265
  });
255
266
  const durationMs = Date.now() - startedAt;
256
- const workerRecords = opts.collectWorkerRecords?.() ?? cellsToRunRecords(result.winnerOnHoldout.cells, "winner", runDir, result.winnerSurface);
257
267
  const { record: provenance } = await emitLoopProvenance({
258
- runId: `${runDir}#${startedAt}`,
259
- runDir,
260
- timestamp: new Date(startedAt).toISOString(),
261
- baselineSurface: opts.baselineSurface,
262
- winnerSurface: result.winnerSurface,
263
- winnerLabel: result.winnerLabel,
264
- winnerRationale: result.winnerRationale,
265
- diff: result.promotedDiff,
266
- baselineSearchComposite: campaignMeanComposite(result.baselineCampaign),
267
- generations: result.generations.map((g) => ({
268
- generationIndex: g.record.generationIndex,
269
- candidates: g.record.candidates,
270
- promoted: g.record.promoted,
271
- surfaces: g.surfaces.map((s) => ({ surfaceHash: s.surfaceHash, surface: s.surface }))
272
- })),
273
- gate: result.gateResult,
274
- baselineOnHoldout: result.baselineOnHoldout,
275
- winnerOnHoldout: result.winnerOnHoldout,
276
- workerRecords,
277
- totalCostUsd: totalCost,
278
- totalDurationMs: durationMs,
268
+ ...loopProvenanceArgsFromResult({
269
+ runId: `${runDir}#${startedAt}`,
270
+ runDir,
271
+ timestamp: new Date(startedAt).toISOString(),
272
+ baselineSurface: opts.baselineSurface,
273
+ result,
274
+ costReceipts: costLedger.list(),
275
+ totalCostUsd: totalCost,
276
+ totalDurationMs: durationMs
277
+ }),
279
278
  storage,
280
279
  hostedClient: opts.hostedTenant ? createHostedClient(opts.hostedTenant) : void 0
281
280
  });
@@ -423,7 +422,13 @@ function cellsToRunRecords(cells, candidateId, runId, surface) {
423
422
  commitSha: "cell",
424
423
  wallMs: cell.durationMs,
425
424
  costUsd: cell.costUsd,
426
- tokenUsage: { input: 0, output: 0 },
425
+ tokenUsage: {
426
+ input: cell.tokenUsage.input,
427
+ output: cell.tokenUsage.output,
428
+ ...cell.tokenUsage.reasoning === void 0 ? {} : { reasoning: cell.tokenUsage.reasoning },
429
+ ...cell.tokenUsage.cached === void 0 ? {} : { cached: cell.tokenUsage.cached },
430
+ ...cell.tokenUsage.cacheWrite === void 0 ? {} : { cacheWrite: cell.tokenUsage.cacheWrite }
431
+ },
427
432
  outcome: {
428
433
  holdoutScore: composite,
429
434
  raw: {},
@@ -541,17 +546,35 @@ function requirePositiveInteger(value, field) {
541
546
  }
542
547
 
543
548
  // src/contract/measured-comparison.ts
544
- import { createHash as createHash2 } from "crypto";
545
549
  import {
546
550
  agentImprovementMeasuredComparisonSchema
547
551
  } from "@tangle-network/agent-interface";
548
552
  function measuredComparisonFromSelfImproveResult(options) {
549
553
  const { result } = options;
554
+ verifyLoopProvenanceRecord(result.provenance);
550
555
  const power = result.power;
551
556
  if (!power) throw new Error("agent improvement comparison requires heldout power analysis");
552
557
  if (result.provenance.gate.reasons.length === 0) {
553
558
  throw new Error("agent improvement comparison requires measured decision reasons");
554
559
  }
560
+ const receiptLedger = new CostLedger({ receipts: result.receipts });
561
+ const receiptCost = receiptLedger.summary();
562
+ const receipts = receiptLedger.list();
563
+ assertRealAgentReceipts(receipts, { allowMixed: false });
564
+ const receiptsById = new Map(receipts.map((receipt) => [receipt.callId, receipt]));
565
+ const receiptIdsByCell = indexReceiptIdsByCell(receipts);
566
+ assertCompleteMeasuredCampaign(
567
+ result.raw.baselineOnHoldout,
568
+ "heldout baseline",
569
+ receiptsById,
570
+ receiptIdsByCell
571
+ );
572
+ assertCompleteMeasuredCampaign(
573
+ result.raw.winnerOnHoldout,
574
+ "heldout candidate",
575
+ receiptsById,
576
+ receiptIdsByCell
577
+ );
555
578
  const pairs = pairMeasuredCells(
556
579
  result.raw.baselineOnHoldout.cells,
557
580
  result.raw.winnerOnHoldout.cells
@@ -569,10 +592,103 @@ function measuredComparisonFromSelfImproveResult(options) {
569
592
  assertMeasuredNumber(result.lift, composite.delta, "heldout lift");
570
593
  assertMeasuredNumber(result.baseline.compositeMean, composite.baseline, "heldout baseline");
571
594
  assertMeasuredNumber(result.winner.compositeMean, composite.candidate, "heldout candidate");
572
- assertMeasuredNumber(result.provenance.heldOutLift, composite.delta, "provenance heldout lift");
573
- if (result.gateDecision !== result.provenance.gate.decision || power.n !== composite.n || power.confidence !== 0.95) {
574
- throw new Error("agent improvement measurement sources do not agree");
595
+ const baselineContentHash = surfaceContentHash(options.baselineSurface);
596
+ const candidateContentHash = surfaceContentHash(result.winner.surface);
597
+ const canonicalDiff = baselineContentHash === candidateContentHash ? "" : renderSurfaceDiff(result.raw.winnerSurface, options.baselineSurface);
598
+ assertMeasuredIdentity(result.gateDecision, result.raw.gateResult.decision, "raw decision");
599
+ assertMeasuredIdentity(
600
+ candidateContentHash,
601
+ surfaceContentHash(result.raw.winnerSurface),
602
+ "raw winner surface"
603
+ );
604
+ assertMeasuredIdentity(result.diff, canonicalDiff, "surface diff");
605
+ assertMeasuredIdentity(result.raw.promotedDiff, canonicalDiff, "raw surface diff");
606
+ if (result.gateDecision === "ship" && (baselineContentHash === candidateContentHash || result.diff.trim().length === 0)) {
607
+ throw new Error("a shipped improvement requires a changed surface and non-empty diff");
608
+ }
609
+ assertMeasuredIdentity(
610
+ canonicalDigest(power),
611
+ canonicalDigest(
612
+ powerPreflight({
613
+ baselineComposites: pairs.map(([cell]) => measuredComposite(cell)),
614
+ sharedScorerChannel: true
615
+ })
616
+ ),
617
+ "power analysis"
618
+ );
619
+ assertMeasuredNumber(power.n, composite.n, "power sample size");
620
+ assertMeasuredNumber(power.confidence, 0.95, "power confidence");
621
+ assertCompleteMeasuredCampaign(
622
+ result.raw.baselineCampaign,
623
+ "search baseline",
624
+ receiptsById,
625
+ receiptIdsByCell
626
+ );
627
+ assertGenerationMeasurements(result.raw.generations, receiptsById, receiptIdsByCell);
628
+ if (result.raw.neutralizedOnHoldout === void 0 !== (result.raw.neutralizedSurface === void 0)) {
629
+ throw new Error("neutralized surface and campaign must be supplied together");
630
+ }
631
+ if (result.raw.neutralizedOnHoldout) {
632
+ assertCompleteMeasuredCampaign(
633
+ result.raw.neutralizedOnHoldout,
634
+ "heldout neutralized",
635
+ receiptsById,
636
+ receiptIdsByCell
637
+ );
638
+ pairMeasuredCells(result.raw.baselineOnHoldout.cells, result.raw.neutralizedOnHoldout.cells);
639
+ }
640
+ const rebuiltProvenance = buildLoopProvenanceRecord(
641
+ loopProvenanceArgsFromResult({
642
+ runId: result.provenance.runId,
643
+ runDir: result.provenance.runDir,
644
+ timestamp: result.provenance.timestamp,
645
+ baselineSurface: options.baselineSurface,
646
+ result: result.raw,
647
+ costReceipts: receipts,
648
+ totalCostUsd: result.totalCostUsd,
649
+ totalDurationMs: result.durationMs
650
+ })
651
+ );
652
+ assertMeasuredIdentity(
653
+ result.provenance.recordDigest,
654
+ rebuiltProvenance.recordDigest,
655
+ "provenance record"
656
+ );
657
+ assertMeasuredIdentity(
658
+ options.benchmark.splitDigest,
659
+ rebuiltProvenance.evidence.holdout.splitDigest,
660
+ "benchmark heldout split"
661
+ );
662
+ const generationsExplored = result.raw.generations.length;
663
+ assertMeasuredNumber(result.generationsExplored, generationsExplored, "generation count");
664
+ assertMeasuredNumber(result.totalCostUsd, result.cost.totalCostUsd, "cost summary");
665
+ assertMeasuredIdentity(
666
+ canonicalDigest(result.cost),
667
+ canonicalDigest(result.raw.cost),
668
+ "raw cost summary"
669
+ );
670
+ if (!result.cost.accountingComplete) {
671
+ throw new Error(
672
+ `cost accounting is incomplete: ${result.cost.incompleteReasons.join("; ") || "unknown reason"}`
673
+ );
674
+ }
675
+ if (!receiptCost.accountingComplete) {
676
+ throw new Error(
677
+ `cost accounting is incomplete: ${receiptCost.incompleteReasons.join("; ") || "unknown reason"}`
678
+ );
575
679
  }
680
+ assertMeasuredIdentity(
681
+ canonicalDigest(result.cost),
682
+ canonicalDigest(receiptCost),
683
+ "cost receipt summary"
684
+ );
685
+ assertMeasuredNumber(result.totalCostUsd, receiptCost.totalCostUsd, "cost receipts");
686
+ assertMeasuredOptional(result.winner.label, result.raw.winnerLabel, "raw winner label");
687
+ assertMeasuredOptional(
688
+ result.winner.rationale,
689
+ result.raw.winnerRationale,
690
+ "raw winner rationale"
691
+ );
576
692
  return agentImprovementMeasuredComparisonSchema.parse({
577
693
  schemaVersion: 1,
578
694
  kind: "agent-improvement-measured-comparison",
@@ -598,8 +714,8 @@ function measuredComparisonFromSelfImproveResult(options) {
598
714
  } : {},
599
715
  decision: {
600
716
  outcome: result.gateDecision,
601
- reasons: result.provenance.gate.reasons,
602
- contributingChecks: result.provenance.gate.contributingGates.map((check) => ({
717
+ reasons: rebuiltProvenance.gate.reasons,
718
+ contributingChecks: rebuiltProvenance.gate.contributingGates.map((check) => ({
603
719
  name: check.name,
604
720
  passed: check.passed
605
721
  }))
@@ -615,20 +731,98 @@ function measuredComparisonFromSelfImproveResult(options) {
615
731
  },
616
732
  provenance: {
617
733
  kind: "agent-eval-loop",
618
- schema: result.provenance.schema,
619
- runId: result.provenance.runId,
620
- recordDigest: digest(result.provenance),
621
- baselineContentHash: result.provenance.baselineContentHash,
622
- candidateContentHash: result.provenance.winnerContentHash
734
+ schema: rebuiltProvenance.schema,
735
+ runId: rebuiltProvenance.runId,
736
+ recordDigest: rebuiltProvenance.recordDigest,
737
+ baselineContentHash,
738
+ candidateContentHash
623
739
  },
624
- diff: result.diff,
740
+ diff: canonicalDiff,
625
741
  evaluation: {
626
- generationsExplored: result.generationsExplored,
742
+ generationsExplored,
627
743
  durationMs: result.durationMs,
628
744
  totalCostUsd: result.totalCostUsd
629
745
  }
630
746
  });
631
747
  }
748
+ function assertGenerationMeasurements(generations, receiptsById, receiptIdsByCell) {
749
+ for (const generation of generations) {
750
+ for (const measured of generation.surfaces) {
751
+ const candidate = generation.record.candidates.find(
752
+ (entry) => entry.surfaceHash === measured.surfaceHash
753
+ );
754
+ if (!candidate) {
755
+ throw new Error(
756
+ `generation ${generation.record.generationIndex} is missing candidate ${measured.surfaceHash}`
757
+ );
758
+ }
759
+ const composite = campaignMeanComposite(measured.campaign);
760
+ const breakdown = campaignBreakdown(measured.campaign);
761
+ const coverage = campaignCoverage(
762
+ measured.campaign.cells,
763
+ measured.campaign.scenarios,
764
+ measured.campaign.reps,
765
+ true
766
+ );
767
+ assertScorableMeasuredCells(
768
+ measured.campaign,
769
+ coverage.scorableCellIds,
770
+ receiptsById,
771
+ receiptIdsByCell
772
+ );
773
+ assertMeasuredNumber(
774
+ candidate.composite,
775
+ composite,
776
+ `candidate ${measured.surfaceHash} composite`
777
+ );
778
+ if (!candidate.coverage) {
779
+ throw new Error(`candidate ${measured.surfaceHash} does not report campaign coverage`);
780
+ }
781
+ assertMeasuredIdentity(
782
+ canonicalDigest(candidate.coverage),
783
+ canonicalDigest({
784
+ expectedCells: coverage.expectedCellIds.length,
785
+ scorableCells: coverage.scorableCellIds.length,
786
+ unscorableCells: coverage.unscorableCells
787
+ }),
788
+ `candidate ${measured.surfaceHash} coverage`
789
+ );
790
+ assertMeasuredIdentity(
791
+ String(candidate.eligibleForPromotion),
792
+ String(coverage.complete),
793
+ `candidate ${measured.surfaceHash} eligibility`
794
+ );
795
+ assertMeasuredIdentity(
796
+ canonicalDigest(candidate.dimensions),
797
+ canonicalDigest(breakdown.dimensions),
798
+ `candidate ${measured.surfaceHash} dimensions`
799
+ );
800
+ assertMeasuredIdentity(
801
+ canonicalDigest(candidate.scenarios),
802
+ canonicalDigest(breakdown.scenarios),
803
+ `candidate ${measured.surfaceHash} scenarios`
804
+ );
805
+ }
806
+ }
807
+ }
808
+ function assertCompleteMeasuredCampaign(campaign, name, receiptsById, receiptIdsByCell) {
809
+ assertCampaignSplitIdentity(campaign.scenarios, campaign.reps, campaign.splitDigest);
810
+ const coverage = campaignCoverage(campaign.cells, campaign.scenarios, campaign.reps, true);
811
+ if (!coverage.complete) {
812
+ throw new Error(
813
+ `${name} is incomplete (${coverage.scorableCellIds.length}/${coverage.expectedCellIds.length} designed cells scorable)`
814
+ );
815
+ }
816
+ assertScorableMeasuredCells(campaign, coverage.scorableCellIds, receiptsById, receiptIdsByCell);
817
+ }
818
+ function assertScorableMeasuredCells(campaign, scorableCellIds, receiptsById, receiptIdsByCell) {
819
+ const cellsById = new Map(campaign.cells.map((cell) => [cell.cellId, cell]));
820
+ for (const cellId of scorableCellIds) {
821
+ const cell = cellsById.get(cellId);
822
+ if (!cell) throw new Error(`measured campaign is missing scorable cell ${cellId}`);
823
+ assertMeasuredCell(cell, receiptsById, receiptIdsByCell, campaign.runDir);
824
+ }
825
+ }
632
826
  function measuredObjectives(pairs) {
633
827
  const qualityColumns = /* @__PURE__ */ new Map();
634
828
  for (const [baseline, candidate] of pairs) {
@@ -673,19 +867,6 @@ function measuredObjectives(pairs) {
673
867
  function measuredCostObjective(pairs) {
674
868
  const cells = pairs.flat();
675
869
  for (const cell of cells) finiteMeasuredValue(cell.costUsd, "cost:cost");
676
- const reported = cells.some(
677
- (cell) => cell.costUsd !== 0 || cell.tokenUsage !== void 0 && (cell.tokenUsage.input > 0 || cell.tokenUsage.output > 0)
678
- );
679
- if (!reported) {
680
- return {
681
- kind: "cost",
682
- name: "cost",
683
- availability: "unavailable",
684
- reason: "heldout cells did not report model usage or cost",
685
- direction: "lower-is-better",
686
- unit: "usd"
687
- };
688
- }
689
870
  return measuredObjective(
690
871
  {
691
872
  kind: "cost",
@@ -705,7 +886,8 @@ function measuredComposite(cell) {
705
886
  return measuredMean(values);
706
887
  }
707
888
  function pairMeasuredCells(baselineCells, candidateCells) {
708
- const errors = [...baselineCells, ...candidateCells].filter((cell) => cell.error);
889
+ const cells = [...baselineCells, ...candidateCells];
890
+ const errors = cells.filter((cell) => cell.error);
709
891
  if (errors.length > 0) {
710
892
  throw new Error(
711
893
  `measured objectives cannot publish ${errors.length} errored heldout cells: ${errors.map((cell) => measuredCellKey(cell)).join(", ")}`
@@ -729,9 +911,88 @@ function pairMeasuredCells(baselineCells, candidateCells) {
729
911
  if (paired.pairs.length === 0 || paired.unpairedBaseline.length > 0 || paired.unpairedTreatment.length > 0) {
730
912
  throw new Error("measured objectives require the same non-empty paired heldout cells");
731
913
  }
732
- return paired.pairs.map(
733
- (pair) => [pair.baseline.cell, pair.treatment.cell]
914
+ return paired.pairs.map((pair) => {
915
+ const baseline = pair.baseline.cell;
916
+ const candidate = pair.treatment.cell;
917
+ if (baseline.seed !== candidate.seed) {
918
+ throw new Error(`heldout cell ${baseline.cellId} does not share one paired seed`);
919
+ }
920
+ return [baseline, candidate];
921
+ });
922
+ }
923
+ function assertMeasuredCell(cell, receiptsById, receiptIdsByCell, runDir) {
924
+ if (!cell.scenarioId.trim() || !Number.isSafeInteger(cell.rep) || cell.rep < 0 || !Number.isSafeInteger(cell.seed) || cell.cellId !== measuredCellKey(cell)) {
925
+ throw new Error(`heldout cell '${cell.cellId}' has an invalid scenario/rep identity`);
926
+ }
927
+ nonnegativeMeasuredValue(cell.costUsd, `heldout cell ${cell.cellId} cost`);
928
+ nonnegativeMeasuredValue(cell.durationMs, `heldout cell ${cell.cellId} latency`);
929
+ if (!cell.tokenUsage) {
930
+ throw new Error(`heldout cell ${cell.cellId} does not report token usage`);
931
+ }
932
+ nonnegativeMeasuredInteger(cell.tokenUsage.input, `heldout cell ${cell.cellId} input tokens`);
933
+ nonnegativeMeasuredInteger(cell.tokenUsage.output, `heldout cell ${cell.cellId} output tokens`);
934
+ if (cell.tokenUsage.cached !== void 0) {
935
+ nonnegativeMeasuredInteger(cell.tokenUsage.cached, `heldout cell ${cell.cellId} cached tokens`);
936
+ }
937
+ if (!Array.isArray(cell.costCallIds)) {
938
+ throw new Error(`heldout cell ${cell.cellId} does not identify its cost receipts`);
939
+ }
940
+ if (new Set(cell.costCallIds).size !== cell.costCallIds.length) {
941
+ throw new Error(`heldout cell ${cell.cellId} repeats a cost receipt`);
942
+ }
943
+ const linkedReceipts = cell.costCallIds.map((callId) => {
944
+ const receipt = receiptsById.get(callId);
945
+ if (!receipt)
946
+ throw new Error(`heldout cell ${cell.cellId} references missing cost receipt ${callId}`);
947
+ if (receipt.tags?.runDir !== runDir || receipt.tags.cellId !== cell.cellId || receipt.tags.scenarioId !== cell.scenarioId || receipt.tags.rep !== String(cell.rep)) {
948
+ throw new Error(`heldout cell ${cell.cellId} references a cost receipt from another cell`);
949
+ }
950
+ return receipt;
951
+ });
952
+ assertMeasuredIdentity(
953
+ canonicalDigest([...cell.costCallIds].sort()),
954
+ canonicalDigest(
955
+ [
956
+ ...receiptIdsByCell.get(receiptCellKey(runDir, cell.cellId, cell.scenarioId, cell.rep)) ?? []
957
+ ].sort()
958
+ ),
959
+ `heldout cell ${cell.cellId} cost receipt IDs`
734
960
  );
961
+ const agentReceipts = linkedReceipts.filter((receipt) => receipt.channel === "agent");
962
+ if (agentReceipts.some((receipt) => receipt.error)) {
963
+ throw new Error(`measured cell ${cell.cellId} links a failed agent receipt`);
964
+ }
965
+ assertRealAgentReceipts(agentReceipts, { allowMixed: false });
966
+ assertMeasuredNumber(
967
+ cell.costUsd,
968
+ agentReceipts.reduce((total, receipt) => total + receipt.costUsd, 0),
969
+ `heldout cell ${cell.cellId} cost receipts`
970
+ );
971
+ assertMeasuredNumber(
972
+ cell.tokenUsage.input,
973
+ agentReceipts.reduce((total, receipt) => total + receipt.inputTokens, 0),
974
+ `heldout cell ${cell.cellId} input receipts`
975
+ );
976
+ assertMeasuredNumber(
977
+ cell.tokenUsage.output,
978
+ agentReceipts.reduce((total, receipt) => total + receipt.outputTokens, 0),
979
+ `heldout cell ${cell.cellId} output receipts`
980
+ );
981
+ assertMeasuredNumber(
982
+ cell.tokenUsage.cached ?? 0,
983
+ agentReceipts.reduce((total, receipt) => total + (receipt.cachedTokens ?? 0), 0),
984
+ `heldout cell ${cell.cellId} cached receipts`
985
+ );
986
+ for (const [judge, score] of Object.entries(cell.judgeScores)) {
987
+ if (score.failed) {
988
+ throw new Error(`heldout cell ${cell.cellId} contains failed judge '${judge}'`);
989
+ }
990
+ finiteMeasuredValue(score.composite, `objective:${judge}`);
991
+ for (const [dimension, value] of Object.entries(score.dimensions)) {
992
+ finiteMeasuredValue(value, `dimension:${judge}:${dimension}`);
993
+ }
994
+ }
995
+ measuredComposite(cell);
735
996
  }
736
997
  function measuredObjective(identity, pairs, value) {
737
998
  const key = measuredObjectiveKey(identity);
@@ -788,6 +1049,16 @@ function finiteMeasuredValue(value, name) {
788
1049
  if (!Number.isFinite(value)) throw new Error(`measured objective '${name}' is not finite`);
789
1050
  return value;
790
1051
  }
1052
+ function nonnegativeMeasuredValue(value, name) {
1053
+ if (!Number.isFinite(value) || value < 0) throw new Error(`${name} must be non-negative`);
1054
+ return value;
1055
+ }
1056
+ function nonnegativeMeasuredInteger(value, name) {
1057
+ if (!Number.isSafeInteger(value) || value < 0) {
1058
+ throw new Error(`${name} must be a non-negative safe integer`);
1059
+ }
1060
+ return value;
1061
+ }
791
1062
  function measuredMean(values) {
792
1063
  if (values.length === 0) throw new Error("measured objective has no paired values");
793
1064
  return values.reduce((sum, value) => sum + value, 0) / values.length;
@@ -805,10 +1076,26 @@ function assertMeasuredNumber(actual, expected, name) {
805
1076
  throw new Error(`${name} does not agree across the measured comparison`);
806
1077
  }
807
1078
  }
808
- function digest(value) {
809
- const json = JSON.stringify(value);
810
- if (json === void 0) throw new Error("agent improvement provenance is not serializable");
811
- return `sha256:${createHash2("sha256").update(canonicalJson(JSON.parse(json))).digest("hex")}`;
1079
+ function assertMeasuredIdentity(actual, expected, name) {
1080
+ if (actual !== expected) throw new Error(`${name} does not agree across the measured comparison`);
1081
+ }
1082
+ function assertMeasuredOptional(actual, expected, name) {
1083
+ if (actual !== expected) throw new Error(`${name} does not agree across the measured comparison`);
1084
+ }
1085
+ function indexReceiptIdsByCell(receipts) {
1086
+ const idsByCell = /* @__PURE__ */ new Map();
1087
+ for (const receipt of receipts) {
1088
+ const tags = receipt.tags;
1089
+ if (!tags?.runDir || !tags.cellId || !tags.scenarioId || tags.rep === void 0) continue;
1090
+ const key = receiptCellKey(tags.runDir, tags.cellId, tags.scenarioId, tags.rep);
1091
+ const ids = idsByCell.get(key) ?? [];
1092
+ ids.push(receipt.callId);
1093
+ idsByCell.set(key, ids);
1094
+ }
1095
+ return idsByCell;
1096
+ }
1097
+ function receiptCellKey(runDir, cellId, scenarioId, rep) {
1098
+ return JSON.stringify([runDir, cellId, scenarioId, String(rep)]);
812
1099
  }
813
1100
 
814
1101
  // src/contract/eval-reporting-suite.ts
@@ -1326,6 +1613,7 @@ export {
1326
1613
  analyzeRuns,
1327
1614
  buildDefaultAnalystRegistry,
1328
1615
  buildEvidenceVector,
1616
+ campaignSplitDigest,
1329
1617
  composeGate,
1330
1618
  createChatClient,
1331
1619
  createReferenceEquivalenceJudge,