@tangle-network/agent-eval 0.137.0 → 0.139.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 (172) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/README.md +34 -0
  3. package/dist/analyst/index.d.ts +485 -104
  4. package/dist/analyst/index.d.ts.map +1 -1
  5. package/dist/analyst/index.js +10 -607
  6. package/dist/analyst/index.js.map +1 -1
  7. package/dist/{benchmark-YDrpumqB.js → benchmark-CYtcIF2V.js} +299 -159
  8. package/dist/benchmark-CYtcIF2V.js.map +1 -0
  9. package/dist/{benchmark-CHX4orG7.d.ts → benchmark-DDVdWcwA.d.ts} +67 -15
  10. package/dist/benchmark-DDVdWcwA.d.ts.map +1 -0
  11. package/dist/benchmark-command-BKfjOBJ5.js +4537 -0
  12. package/dist/benchmark-command-BKfjOBJ5.js.map +1 -0
  13. package/dist/benchmarks/index.d.ts +1 -1
  14. package/dist/benchmarks/index.js +1 -1
  15. package/dist/{benchmarks-DCLkQOmc.js → benchmarks-zxhy1QV3.js} +4 -3
  16. package/dist/{benchmarks-DCLkQOmc.js.map → benchmarks-zxhy1QV3.js.map} +1 -1
  17. package/dist/campaign/index.d.ts +5 -5
  18. package/dist/campaign/index.js +4 -3
  19. package/dist/{campaign-lgObcHFC.js → campaign-DrS6_hLd.js} +19 -11
  20. package/dist/campaign-DrS6_hLd.js.map +1 -0
  21. package/dist/canonical-D011XM8r.js +86 -0
  22. package/dist/canonical-D011XM8r.js.map +1 -0
  23. package/dist/cli.js +10 -3
  24. package/dist/cli.js.map +1 -1
  25. package/dist/{client-C8L6h6Wf.d.ts → client-BohnDFBq.d.ts} +4 -4
  26. package/dist/{client-C8L6h6Wf.d.ts.map → client-BohnDFBq.d.ts.map} +1 -1
  27. package/dist/{completion-verifier-DSyRNVzU.d.ts → completion-verifier-IPoP4fQO.d.ts} +178 -4
  28. package/dist/completion-verifier-IPoP4fQO.d.ts.map +1 -0
  29. package/dist/contract/index.d.ts +10 -10
  30. package/dist/contract/index.js +9 -8
  31. package/dist/contract/index.js.map +1 -1
  32. package/dist/control.d.ts +2 -2
  33. package/dist/{cost-ledger-D-5_-dhi.js → cost-ledger-CZ9diLxY.js} +90 -45
  34. package/dist/cost-ledger-CZ9diLxY.js.map +1 -0
  35. package/dist/{cost-ledger-D2o6JOrL.d.ts → cost-ledger-DKgyIWRj.d.ts} +6 -2
  36. package/dist/cost-ledger-DKgyIWRj.d.ts.map +1 -0
  37. package/dist/default-registry-B8vf7Rmf.d.ts +118 -0
  38. package/dist/default-registry-B8vf7Rmf.d.ts.map +1 -0
  39. package/dist/default-registry-BgJJItGr.js +2364 -0
  40. package/dist/default-registry-BgJJItGr.js.map +1 -0
  41. package/dist/dspy-rlm-engine-DTkVyDX-.js +344 -0
  42. package/dist/dspy-rlm-engine-DTkVyDX-.js.map +1 -0
  43. package/dist/{eval-campaign-CHqfLnff.js → eval-campaign-BmptJj50.js} +2 -2
  44. package/dist/{eval-campaign-CHqfLnff.js.map → eval-campaign-BmptJj50.js.map} +1 -1
  45. package/dist/exact-types-MaaFcllV.d.ts +234 -0
  46. package/dist/exact-types-MaaFcllV.d.ts.map +1 -0
  47. package/dist/external-optimizer-contracts-BrxY2Sli.d.ts +32 -0
  48. package/dist/external-optimizer-contracts-BrxY2Sli.d.ts.map +1 -0
  49. package/dist/{extract-usage-p-56bh8q.js → extract-usage-DZs601Va.js} +2 -2
  50. package/dist/{extract-usage-p-56bh8q.js.map → extract-usage-DZs601Va.js.map} +1 -1
  51. package/dist/{feedback-trajectory-N_F0PwHz.d.ts → feedback-trajectory-BJUWOkJM.d.ts} +3 -2
  52. package/dist/feedback-trajectory-BJUWOkJM.d.ts.map +1 -0
  53. package/dist/fuzz.d.ts +1 -1
  54. package/dist/fuzz.js +1 -1
  55. package/dist/{hf-dataset-DBJXXoY1.js → hf-dataset-XggBupCr.js} +2 -2
  56. package/dist/{hf-dataset-DBJXXoY1.js.map → hf-dataset-XggBupCr.js.map} +1 -1
  57. package/dist/hosted/index.d.ts +3 -3
  58. package/dist/{index-C-Pr4OWg.d.ts → index-BTm_P9aC.d.ts} +12 -11
  59. package/dist/index-BTm_P9aC.d.ts.map +1 -0
  60. package/dist/{index-U3RHOShi.d.ts → index-CWOPCJiw.d.ts} +2 -2
  61. package/dist/{index-U3RHOShi.d.ts.map → index-CWOPCJiw.d.ts.map} +1 -1
  62. package/dist/{index-DRNl6g_N.d.ts → index-CtR1xh4V.d.ts} +3 -3
  63. package/dist/{index-DRNl6g_N.d.ts.map → index-CtR1xh4V.d.ts.map} +1 -1
  64. package/dist/index-DEb46kc6.d.ts.map +1 -1
  65. package/dist/{index-BnP1QJUv.d.ts → index-_66rVpwN.d.ts} +5 -5
  66. package/dist/{index-BnP1QJUv.d.ts.map → index-_66rVpwN.d.ts.map} +1 -1
  67. package/dist/index.d.ts +35 -55
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +55 -514
  70. package/dist/index.js.map +1 -1
  71. package/dist/{insight-report-B9ooYH_g.d.ts → insight-report-Bu5Wi9tG.d.ts} +4 -4
  72. package/dist/{insight-report-B9ooYH_g.d.ts.map → insight-report-Bu5Wi9tG.d.ts.map} +1 -1
  73. package/dist/{integrity-CKxosZ5Z.d.ts → integrity-COTh3DTH.d.ts} +2 -2
  74. package/dist/{integrity-CKxosZ5Z.d.ts.map → integrity-COTh3DTH.d.ts.map} +1 -1
  75. package/dist/kind-factory-CFxA0JQX.js +2133 -0
  76. package/dist/kind-factory-CFxA0JQX.js.map +1 -0
  77. package/dist/ledger-core/index.js +2 -1
  78. package/dist/{ledger-core-t6sItivm.js → ledger-core-Dxz0Rkwa.js} +210 -99
  79. package/dist/ledger-core-Dxz0Rkwa.js.map +1 -0
  80. package/dist/{llm-client-DKB25jV8.js → llm-client-bkztEfIx.js} +5 -5
  81. package/dist/llm-client-bkztEfIx.js.map +1 -0
  82. package/dist/meta-eval/index.d.ts +2 -2
  83. package/dist/multishot/index.d.ts +2 -2
  84. package/dist/openapi.json +1 -1
  85. package/dist/{proposal-findings-DCawte-y.js → proposal-findings-2GIUo1et.js} +2 -68
  86. package/dist/proposal-findings-2GIUo1et.js.map +1 -0
  87. package/dist/{release-report-CofgVNZt.d.ts → release-report-fZarvIm-.d.ts} +3 -3
  88. package/dist/{release-report-CofgVNZt.d.ts.map → release-report-fZarvIm-.d.ts.map} +1 -1
  89. package/dist/{replay-K8FaC0CB.d.ts → replay-DjG4IG60.d.ts} +34 -143
  90. package/dist/replay-DjG4IG60.d.ts.map +1 -0
  91. package/dist/{replay-Bju0T8Ls.js → replay-SA4OB7O7.js} +48 -136
  92. package/dist/replay-SA4OB7O7.js.map +1 -0
  93. package/dist/reporting.d.ts +4 -4
  94. package/dist/{researcher-Da0Wj-bt.d.ts → researcher-BxhtGfKa.d.ts} +5 -5
  95. package/dist/{researcher-Da0Wj-bt.d.ts.map → researcher-BxhtGfKa.d.ts.map} +1 -1
  96. package/dist/{reward-hacking-CQ3hTCO3.d.ts → reward-hacking-CqSLiV51.d.ts} +2 -2
  97. package/dist/{reward-hacking-CQ3hTCO3.d.ts.map → reward-hacking-CqSLiV51.d.ts.map} +1 -1
  98. package/dist/rl.d.ts +5 -5
  99. package/dist/rl.js +1 -1
  100. package/dist/rollout/index.d.ts +1 -1
  101. package/dist/rollout/index.js +2 -2
  102. package/dist/{rollout-DQFl0UXA.js → rollout-8nj3mYvx.js} +2 -2
  103. package/dist/{rollout-DQFl0UXA.js.map → rollout-8nj3mYvx.js.map} +1 -1
  104. package/dist/{rubric-predictive-validity-C4sztLR3.d.ts → rubric-predictive-validity-DQBQj6uV.d.ts} +2 -2
  105. package/dist/{rubric-predictive-validity-C4sztLR3.d.ts.map → rubric-predictive-validity-DQBQj6uV.d.ts.map} +1 -1
  106. package/dist/{run-evidence-BDIircdA.d.ts → run-evidence-C4RcRQT5.d.ts} +3 -3
  107. package/dist/{run-evidence-BDIircdA.d.ts.map → run-evidence-C4RcRQT5.d.ts.map} +1 -1
  108. package/dist/{run-record-BPCa2rQ8.d.ts → run-record-CztDMXVF.d.ts} +2 -2
  109. package/dist/{run-record-BPCa2rQ8.d.ts.map → run-record-CztDMXVF.d.ts.map} +1 -1
  110. package/dist/{semantic-concept-judge-Bz64IckK.js → semantic-concept-judge-BuIJ9IfB.js} +49 -6
  111. package/dist/semantic-concept-judge-BuIJ9IfB.js.map +1 -0
  112. package/dist/{server-KjXZZUDX.js → server-DaCpLfi0.js} +3 -3
  113. package/dist/{server-KjXZZUDX.js.map → server-DaCpLfi0.js.map} +1 -1
  114. package/dist/single-run-lock-BTTtPZ9N.js +989 -0
  115. package/dist/single-run-lock-BTTtPZ9N.js.map +1 -0
  116. package/dist/{skill-usage-CFDLLlhF.d.ts → skill-usage-B-BFS8M2.d.ts} +65 -40
  117. package/dist/skill-usage-B-BFS8M2.d.ts.map +1 -0
  118. package/dist/{skillopt-optimization-method-f4o9sUT4.js → skillopt-optimization-method-BbGnCC53.js} +20 -979
  119. package/dist/skillopt-optimization-method-BbGnCC53.js.map +1 -0
  120. package/dist/{skillopt-optimization-method-BpbnlvAZ.d.ts → skillopt-optimization-method-_s0Tub7Y.d.ts} +11 -39
  121. package/dist/skillopt-optimization-method-_s0Tub7Y.d.ts.map +1 -0
  122. package/dist/{statistics-_7P642CN.d.ts → statistics-B5d0Zd-z.d.ts} +2 -2
  123. package/dist/{statistics-_7P642CN.d.ts.map → statistics-B5d0Zd-z.d.ts.map} +1 -1
  124. package/dist/store-otlp-DX4fGIcf.js +757 -0
  125. package/dist/store-otlp-DX4fGIcf.js.map +1 -0
  126. package/dist/{summary-report-DHipz9Kx.d.ts → summary-report-Cg7BifAM.d.ts} +3 -3
  127. package/dist/{summary-report-DHipz9Kx.d.ts.map → summary-report-Cg7BifAM.d.ts.map} +1 -1
  128. package/dist/tool-groups-CdYq22lX.d.ts +258 -0
  129. package/dist/tool-groups-CdYq22lX.d.ts.map +1 -0
  130. package/dist/traces.d.ts +7 -6
  131. package/dist/traces.js +5 -4
  132. package/dist/{types-CTGbIm57.d.ts → types-BBFNHxSK.d.ts} +5 -5
  133. package/dist/{types-CTGbIm57.d.ts.map → types-BBFNHxSK.d.ts.map} +1 -1
  134. package/dist/{types-CKswbJGO.d.ts → types-DoEYskCd.d.ts} +5 -5
  135. package/dist/{types-CKswbJGO.d.ts.map → types-DoEYskCd.d.ts.map} +1 -1
  136. package/dist/{types-CTvKfr5F.d.ts → types-uPrS6mD-.d.ts} +2 -2
  137. package/dist/{types-CTvKfr5F.d.ts.map → types-uPrS6mD-.d.ts.map} +1 -1
  138. package/dist/usage-receipt-CgxMEBZq.js +134 -0
  139. package/dist/usage-receipt-CgxMEBZq.js.map +1 -0
  140. package/dist/wire/index.d.ts +3 -3
  141. package/dist/wire/index.js +1 -1
  142. package/docs/trace-analysis.md +191 -385
  143. package/package.json +5 -4
  144. package/dist/analyze-runs-PVtnfjvA.d.ts +0 -72
  145. package/dist/analyze-runs-PVtnfjvA.d.ts.map +0 -1
  146. package/dist/benchmark-CHX4orG7.d.ts.map +0 -1
  147. package/dist/benchmark-YDrpumqB.js.map +0 -1
  148. package/dist/campaign-lgObcHFC.js.map +0 -1
  149. package/dist/completion-verifier-DSyRNVzU.d.ts.map +0 -1
  150. package/dist/concurrency-MUjT7VjM.js +0 -109
  151. package/dist/concurrency-MUjT7VjM.js.map +0 -1
  152. package/dist/cost-ledger-D-5_-dhi.js.map +0 -1
  153. package/dist/cost-ledger-D2o6JOrL.d.ts.map +0 -1
  154. package/dist/default-registry-CLXbRt0f.js +0 -2594
  155. package/dist/default-registry-CLXbRt0f.js.map +0 -1
  156. package/dist/default-registry-Dc5D_Loc.d.ts +0 -202
  157. package/dist/default-registry-Dc5D_Loc.d.ts.map +0 -1
  158. package/dist/feedback-trajectory-N_F0PwHz.d.ts.map +0 -1
  159. package/dist/index-C-Pr4OWg.d.ts.map +0 -1
  160. package/dist/ledger-core-t6sItivm.js.map +0 -1
  161. package/dist/llm-client-DKB25jV8.js.map +0 -1
  162. package/dist/proposal-findings-DCawte-y.js.map +0 -1
  163. package/dist/registry-BdM7SuTr.d.ts +0 -124
  164. package/dist/registry-BdM7SuTr.d.ts.map +0 -1
  165. package/dist/replay-Bju0T8Ls.js.map +0 -1
  166. package/dist/replay-K8FaC0CB.d.ts.map +0 -1
  167. package/dist/semantic-concept-judge-Bz64IckK.js.map +0 -1
  168. package/dist/skill-usage-CFDLLlhF.d.ts.map +0 -1
  169. package/dist/skillopt-optimization-method-BpbnlvAZ.d.ts.map +0 -1
  170. package/dist/skillopt-optimization-method-f4o9sUT4.js.map +0 -1
  171. package/dist/tools-DZk2Jn64.js +0 -1876
  172. package/dist/tools-DZk2Jn64.js.map +0 -1
@@ -1,24 +1,9 @@
1
+ import { t as assertValidAnalystUsageReceipt } from "./usage-receipt-CgxMEBZq.js";
1
2
  import { performance } from "node:perf_hooks";
2
3
  import { linearSumAssignment } from "linear-sum-assignment";
3
- //#region src/analyst/benchmark.ts
4
- /**
5
- * Resolve canonical `trace://<trace>/span/<span>` evidence against a trace store.
6
- * Other evidence kinds and URI schemes require a caller-supplied resolver.
7
- */
8
- function traceStoreEvidenceResolver(getStore) {
9
- return async ({ caseInput, evidence }) => {
10
- if (evidence.kind !== "span") return false;
11
- const location = parseTraceSpanUri(evidence.uri);
12
- if (!location) return false;
13
- const result = await getStore(caseInput).viewSpans({
14
- trace_id: location.traceId,
15
- span_ids: [location.spanId]
16
- });
17
- return result.trace_id === location.traceId && result.missing_span_ids.length === 0 && result.spans.some((span) => span.span_id === location.spanId);
18
- };
19
- }
4
+ //#region src/analyst/benchmark-scoring.ts
20
5
  function scoreAnalystFindings(testCase, findings) {
21
- validateCase(testCase);
6
+ assertValidAnalystScoringCase(testCase);
22
7
  const matchedFindingByIssue = matchFindingsToIssues(testCase.expectedIssues, findings);
23
8
  const matchedIssueIds = testCase.expectedIssues.filter((_, index) => matchedFindingByIssue.has(index)).map((issue) => issue.id);
24
9
  const missedIssueIds = testCase.expectedIssues.filter((_, index) => !matchedFindingByIssue.has(index)).map((issue) => issue.id);
@@ -27,7 +12,7 @@ function scoreAnalystFindings(testCase, findings) {
27
12
  const expectedIssueCount = testCase.expectedIssues.length;
28
13
  const issueRecall = expectedIssueCount === 0 ? 1 : matchedIssueIds.length / expectedIssueCount;
29
14
  const findingPrecision = findings.length === 0 ? expectedIssueCount === 0 ? 1 : 0 : supportedFindingIndexes.size / findings.length;
30
- const f1 = harmonicMean(findingPrecision, issueRecall);
15
+ const f1 = harmonicMeanScore(findingPrecision, issueRecall);
31
16
  const allEvidence = findings.flatMap((finding) => finding.evidence_refs);
32
17
  const criticalIssues = testCase.expectedIssues.filter((issue) => (issue.criticalEvidence?.length ?? 0) > 0);
33
18
  const criticalHits = testCase.expectedIssues.filter((issue) => {
@@ -48,10 +33,25 @@ function scoreAnalystFindings(testCase, findings) {
48
33
  f1,
49
34
  criticalStepAccuracy: criticalIssues.length === 0 ? null : criticalHits / criticalIssues.length,
50
35
  citationCoverage: findings.length === 0 ? null : findingsWithEvidence / findings.length,
36
+ citationExcerptCoverage: allEvidence.length === 0 ? null : allEvidence.filter((evidence) => Boolean(evidence.excerpt?.trim())).length / allEvidence.length,
51
37
  citationLabelAgreement: testCase.labeledEvidence === void 0 ? null : allEvidence.length === 0 ? findings.length === 0 ? null : 0 : (allEvidence.length - unlabeledEvidence.length) / allEvidence.length,
52
- cleanFalsePositive: expectedIssueCount === 0 && findings.length > 0
38
+ predictionOnLabelEmptyCase: expectedIssueCount === 0 && findings.length > 0
53
39
  };
54
40
  }
41
+ function assertValidAnalystScoringCase(testCase) {
42
+ if (!testCase.id.trim()) throw new TypeError("analyst benchmark case id must not be empty");
43
+ const ids = /* @__PURE__ */ new Set();
44
+ for (const issue of testCase.expectedIssues) {
45
+ if (!issue.id.trim()) throw new TypeError(`${testCase.id}: expected issue id must not be empty`);
46
+ if (ids.has(issue.id)) throw new TypeError(`${testCase.id}: duplicate expected issue id '${issue.id}'`);
47
+ ids.add(issue.id);
48
+ if (!issue.findingIds?.length && !issue.areas?.length && !issue.subjects?.length && !issue.evidence?.length) throw new TypeError(`${testCase.id}/${issue.id}: expected issue must identify a finding by id, area, subject, or evidence`);
49
+ }
50
+ for (const ref of testCase.labeledEvidence ?? []) if (!ref.uri.trim()) throw new TypeError(`${testCase.id}: labeled evidence URI must not be empty`);
51
+ }
52
+ function harmonicMeanScore(a, b) {
53
+ return a + b === 0 ? 0 : 2 * a * b / (a + b);
54
+ }
55
55
  function matchFindingsToIssues(issues, findings) {
56
56
  if (issues.length === 0) return /* @__PURE__ */ new Map();
57
57
  const cardinalityWeight = issues.length + 1;
@@ -68,23 +68,218 @@ function matchFindingsToIssues(issues, findings) {
68
68
  }
69
69
  return matches;
70
70
  }
71
+ function findingMatchesIssue(finding, issue) {
72
+ if (issue.findingIds && !issue.findingIds.includes(finding.finding_id)) return false;
73
+ if (issue.areas && !issue.areas.includes(finding.area)) return false;
74
+ if (issue.subjects && (!finding.subject || !issue.subjects.includes(finding.subject))) return false;
75
+ if (issue.evidence && !matchesEvidence(finding.evidence_refs, issue.evidence, issue.evidenceMode ?? "any")) return false;
76
+ return true;
77
+ }
78
+ function matchesEvidence(actual, expected, mode) {
79
+ if (expected.length === 0) return true;
80
+ const match = (target) => actual.some((ref) => evidenceMatches(ref, target));
81
+ return mode === "all" ? expected.every(match) : expected.some(match);
82
+ }
83
+ function evidenceMatches(actual, expected) {
84
+ return actual.uri === expected.uri && (expected.kind === void 0 || actual.kind === expected.kind);
85
+ }
86
+ //#endregion
87
+ //#region src/analyst/benchmark-summary.ts
88
+ function summarizeAnalystBenchmarkRunner(runnerId, observations) {
89
+ const issueBearing = observations.filter((observation) => observation.labelState === "positive");
90
+ const completed = observations.filter((observation) => !observation.error);
91
+ const expectedIssues = issueBearing.reduce((sum, observation) => sum + observation.score.expectedIssueCount, 0);
92
+ const matchedIssues = issueBearing.reduce((sum, observation) => sum + observation.score.matchedIssueIds.length, 0);
93
+ const issueFindings = issueBearing.reduce((sum, observation) => sum + (observation.error ? 0 : observation.findings.length), 0);
94
+ const supportedFindings = issueBearing.reduce((sum, observation) => sum + observation.score.supportedFindingIndexes.length, 0);
95
+ const issueRecall = expectedIssues === 0 ? null : matchedIssues / expectedIssues;
96
+ const findingPrecision = expectedIssues === 0 ? null : issueFindings === 0 ? 0 : supportedFindings / issueFindings;
97
+ const macroIssueRecall = issueBearing.length === 0 ? null : mean(issueBearing.map((observation) => observation.score.issueRecall));
98
+ const macroFindingPrecision = issueBearing.length === 0 ? null : mean(issueBearing.map((observation) => observation.score.findingPrecision));
99
+ const macroF1 = issueBearing.length === 0 ? null : mean(issueBearing.map((observation) => observation.score.f1));
100
+ const critical = observations.map((observation) => observation.score.criticalStepAccuracy).filter((value) => value !== null);
101
+ const findingsWithEvidence = completed.reduce((sum, observation) => sum + observation.findings.filter((finding) => finding.evidence_refs.length > 0).length, 0);
102
+ const allFindings = completed.reduce((sum, observation) => sum + observation.findings.length, 0);
103
+ const allCitations = completed.flatMap((observation) => observation.findings.flatMap((finding) => finding.evidence_refs));
104
+ const citationObservations = completed.filter((observation) => observation.score.citationLabelAgreement !== null);
105
+ const citationCount = citationObservations.reduce((sum, observation) => sum + observation.findings.reduce((count, finding) => count + finding.evidence_refs.length, 0), 0);
106
+ const invalidCitationCount = citationObservations.reduce((sum, observation) => sum + observation.score.unlabeledEvidence.length, 0);
107
+ const trustedNegative = observations.filter((observation) => observation.labelState === "trusted-negative");
108
+ const completedTrustedNegative = trustedNegative.filter((observation) => !observation.error);
109
+ const unlabeled = observations.filter((observation) => observation.labelState === "unlabeled");
110
+ const completedUnlabeled = unlabeled.filter((observation) => !observation.error);
111
+ const resolvedCitations = completed.reduce((sum, observation) => sum + (observation.evidenceResolution?.resolved ?? 0), 0);
112
+ const unresolvedCitations = completed.reduce((sum, observation) => sum + (observation.evidenceResolution?.unresolvedEvidence.length ?? 0), 0);
113
+ const citationResolutionErrors = completed.reduce((sum, observation) => sum + (observation.evidenceResolution?.errors.length ?? 0), 0);
114
+ const resolutionAttempts = completed.filter((observation) => observation.findings.some((finding) => finding.evidence_refs.length > 0));
115
+ const citationResolutionUnknownRuns = resolutionAttempts.filter((observation) => !observation.evidenceResolution || observation.evidenceResolution.errors.length > 0).length;
116
+ const usages = observations.map((observation) => observation.usage);
117
+ const knownCostUsd = stableSum(usages.map((usage) => {
118
+ if (!usage) return 0;
119
+ return usage.cost.kind === "uncaptured" ? usage.knownCostUsd ?? 0 : usage.cost.usd;
120
+ }));
121
+ const predictionAgreement = repeatedObservationAgreement(observations, predictionSignature);
122
+ const matchedLabelAgreement = repeatedObservationAgreement(observations.filter((observation) => observation.labelState === "positive"), matchedLabelSignature);
123
+ return {
124
+ runnerId,
125
+ plannedRuns: observations.length,
126
+ completedRuns: observations.filter((observation) => !observation.error).length,
127
+ failedRuns: observations.filter((observation) => Boolean(observation.error)).length,
128
+ issueBearingRuns: issueBearing.length,
129
+ trustedNegativeRuns: trustedNegative.length,
130
+ unlabeledRuns: unlabeled.length,
131
+ issueRecall,
132
+ findingPrecision,
133
+ f1: findingPrecision === null || issueRecall === null ? null : harmonicMeanScore(findingPrecision, issueRecall),
134
+ macroIssueRecall,
135
+ macroFindingPrecision,
136
+ macroF1,
137
+ criticalStepAccuracy: critical.length === 0 ? null : mean(critical),
138
+ citationCoverage: allFindings === 0 ? null : findingsWithEvidence / allFindings,
139
+ citationExcerptCoverage: allCitations.length === 0 ? null : allCitations.filter((evidence) => Boolean(evidence.excerpt?.trim())).length / allCitations.length,
140
+ citationLabelAgreement: citationObservations.length === 0 ? null : citationCount === 0 ? 0 : (citationCount - invalidCitationCount) / citationCount,
141
+ citationResolution: resolutionAttempts.length === 0 || citationResolutionUnknownRuns > 0 || resolvedCitations + unresolvedCitations === 0 ? null : resolvedCitations / (resolvedCitations + unresolvedCitations),
142
+ citationResolutionUnknownRuns,
143
+ unresolvedCitations,
144
+ citationResolutionErrors,
145
+ trustedNegativeFalsePositiveRate: completedTrustedNegative.length === 0 ? null : completedTrustedNegative.filter((observation) => observation.score.predictionOnLabelEmptyCase).length / completedTrustedNegative.length,
146
+ trustedNegativeFailureRate: trustedNegative.length === 0 ? null : trustedNegative.filter((observation) => Boolean(observation.error)).length / trustedNegative.length,
147
+ unlabeledPredictionRate: completedUnlabeled.length === 0 ? null : completedUnlabeled.filter((observation) => observation.findings.length > 0).length / completedUnlabeled.length,
148
+ unlabeledFailureRate: unlabeled.length === 0 ? null : unlabeled.filter((observation) => Boolean(observation.error)).length / unlabeled.length,
149
+ predictionAgreement: predictionAgreement.value,
150
+ predictionAgreementCases: predictionAgreement.cases,
151
+ matchedLabelAgreement: matchedLabelAgreement.value,
152
+ matchedLabelAgreementCases: matchedLabelAgreement.cases,
153
+ latencyMs: latencyDistribution(observations.map((observation) => observation.latencyMs).filter((value) => value !== null)),
154
+ benchmarkClockLatencyRuns: observations.filter((observation) => observation.latencySource === "benchmark-clock").length,
155
+ runnerReportedLatencyRuns: observations.filter((observation) => observation.latencySource === "runner-reported").length,
156
+ latencyUnknownRuns: observations.filter((observation) => observation.latencySource === "uncaptured").length,
157
+ calls: usages.reduce((sum, usage) => sum + (usage?.calls ?? 0), 0),
158
+ callsUnknownRuns: usages.filter((usage) => !usage || usage.calls === null).length,
159
+ inputTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.input ?? 0), 0),
160
+ outputTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.output ?? 0), 0),
161
+ reasoningTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.reasoning ?? 0), 0),
162
+ cachedTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.cached ?? 0), 0),
163
+ cacheWriteTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.cacheWrite ?? 0), 0),
164
+ tokenUsageUnknownRuns: usages.filter((usage) => !usage?.tokens).length,
165
+ reasoningTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.reasoning === void 0).length,
166
+ cachedTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.cached === void 0).length,
167
+ cacheWriteTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.cacheWrite === void 0).length,
168
+ knownCostUsd,
169
+ costUnknownRuns: usages.filter((usage) => !usage || usage.cost.kind === "uncaptured").length
170
+ };
171
+ }
172
+ function mean(values) {
173
+ return values.length === 0 ? 0 : stableSum(values) / values.length;
174
+ }
175
+ function stableSum(values) {
176
+ const ordered = [...values].sort((left, right) => Math.abs(left) - Math.abs(right) || left - right);
177
+ let sum = 0;
178
+ let correction = 0;
179
+ for (const value of ordered) {
180
+ const next = sum + value;
181
+ correction += Math.abs(sum) >= Math.abs(value) ? sum - next + value : value - next + sum;
182
+ sum = next;
183
+ }
184
+ return sum + correction;
185
+ }
186
+ function latencyDistribution(values) {
187
+ if (values.length === 0) return null;
188
+ const sorted = [...values].sort((a, b) => a - b);
189
+ return {
190
+ min: sorted[0],
191
+ mean: mean(sorted),
192
+ p50: percentile(sorted, .5),
193
+ p95: percentile(sorted, .95),
194
+ max: sorted.at(-1)
195
+ };
196
+ }
197
+ function percentile(sorted, quantile) {
198
+ if (sorted.length === 0) return 0;
199
+ return sorted[Math.ceil(quantile * sorted.length) - 1] ?? sorted.at(-1) ?? 0;
200
+ }
201
+ function repeatedObservationAgreement(observations, signature) {
202
+ const byCase = /* @__PURE__ */ new Map();
203
+ for (const observation of observations) {
204
+ const rows = byCase.get(observation.caseId) ?? [];
205
+ rows.push(observation);
206
+ byCase.set(observation.caseId, rows);
207
+ }
208
+ const caseAgreements = [];
209
+ for (const rows of byCase.values()) {
210
+ const agreements = [];
211
+ for (let left = 0; left < rows.length; left++) for (let right = left + 1; right < rows.length; right++) agreements.push(jaccard(signature(rows[left]), signature(rows[right])));
212
+ if (agreements.length > 0) caseAgreements.push(mean(agreements));
213
+ }
214
+ return {
215
+ value: caseAgreements.length === 0 ? null : mean(caseAgreements),
216
+ cases: caseAgreements.length
217
+ };
218
+ }
219
+ function matchedLabelSignature(observation) {
220
+ if (observation.error) return [`error:${observation.error.class}`];
221
+ return observation.score.matchedIssueIds;
222
+ }
223
+ function predictionSignature(observation) {
224
+ if (observation.error) return [`error:${observation.error.class}`];
225
+ return observation.findings.map((finding) => JSON.stringify([finding.finding_id, finding.evidence_refs.map((evidence) => [
226
+ evidence.kind,
227
+ evidence.uri,
228
+ evidence.excerpt ?? null
229
+ ]).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)))])).sort();
230
+ }
231
+ function jaccard(left, right) {
232
+ const a = new Set(left);
233
+ const b = new Set(right);
234
+ const union = /* @__PURE__ */ new Set([...a, ...b]);
235
+ if (union.size === 0) return 1;
236
+ let intersection = 0;
237
+ for (const value of a) if (b.has(value)) intersection += 1;
238
+ return intersection / union.size;
239
+ }
240
+ //#endregion
241
+ //#region src/analyst/benchmark.ts
242
+ /**
243
+ * Resolve canonical `trace://<trace>/span/<span>` evidence against a trace store.
244
+ * Other evidence kinds and URI schemes require a caller-supplied resolver.
245
+ */
246
+ function traceStoreEvidenceResolver(getStore) {
247
+ return async ({ caseInput, evidence, signal }) => {
248
+ if (evidence.kind !== "span") return false;
249
+ const location = parseTraceSpanUri(evidence.uri);
250
+ if (!location) return false;
251
+ const result = await getStore(caseInput).viewSpans({
252
+ trace_id: location.traceId,
253
+ span_ids: [location.spanId]
254
+ }, signal ? { signal } : void 0);
255
+ return result.trace_id === location.traceId && result.missing_span_ids.length === 0 && result.spans.some((span) => span.span_id === location.spanId);
256
+ };
257
+ }
71
258
  async function runAnalystBenchmark(options) {
72
259
  validateBenchmarkOptions(options);
73
260
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
74
261
  const repetitions = options.repetitions ?? 1;
75
262
  const runnerOrderSeed = options.runnerOrderSeed ?? 0;
76
- const maxConcurrency = Math.min(options.maxConcurrency ?? 1, options.runners.length * options.cases.length * repetitions);
77
- const jobs = benchmarkJobs(options.cases, options.runners, repetitions, runnerOrderSeed);
78
- const observations = [];
263
+ const allJobs = benchmarkJobs(options.cases, options.runners, repetitions, runnerOrderSeed);
264
+ const maxConcurrency = Math.min(options.maxConcurrency ?? 1, allJobs.length);
265
+ const initialObservations = validateInitialObservations(options.initialObservations ?? [], allJobs);
266
+ const completed = new Set(initialObservations.map(observationKey));
267
+ const jobs = allJobs.filter((job) => !completed.has(jobKey(job)));
268
+ const observations = [...initialObservations];
79
269
  let cursor = 0;
80
270
  const worker = async () => {
81
271
  while (cursor < jobs.length) {
82
272
  options.signal?.throwIfAborted();
83
273
  const job = jobs[cursor++];
84
- observations.push(await runBenchmarkJob(job, options.signal, options.resolveEvidence));
274
+ const observation = await runBenchmarkJob(job, options.signal, options.resolveEvidence);
275
+ options.signal?.throwIfAborted();
276
+ await options.onObservation?.(observation);
277
+ options.signal?.throwIfAborted();
278
+ observations.push(observation);
85
279
  }
86
280
  };
87
- await Promise.all(Array.from({ length: maxConcurrency }, worker));
281
+ await Promise.all(Array.from({ length: Math.min(maxConcurrency, jobs.length) }, worker));
282
+ options.signal?.throwIfAborted();
88
283
  const runnerOrder = new Map(options.runners.map((runner, index) => [runner.id, index]));
89
284
  const caseOrder = new Map(options.cases.map((testCase, index) => [testCase.id, index]));
90
285
  observations.sort((a, b) => (runnerOrder.get(a.runnerId) ?? 0) - (runnerOrder.get(b.runnerId) ?? 0) || (caseOrder.get(a.caseId) ?? 0) - (caseOrder.get(b.caseId) ?? 0) || a.repetition - b.repetition);
@@ -100,7 +295,7 @@ async function runAnalystBenchmark(options) {
100
295
  runnerOrderSeed
101
296
  },
102
297
  observations,
103
- summaries: options.runners.map((runner) => summarizeRunner(runner.id, observations.filter((observation) => observation.runnerId === runner.id)))
298
+ summaries: options.runners.map((runner) => summarizeAnalystBenchmarkRunner(runner.id, observations.filter((observation) => observation.runnerId === runner.id)))
104
299
  };
105
300
  }
106
301
  function registryBenchmarkRunner(options) {
@@ -114,7 +309,8 @@ function registryBenchmarkRunner(options) {
114
309
  return {
115
310
  findings: result.findings,
116
311
  usage: mergeRegistryUsage(result),
117
- metadata: { analystRun: result }
312
+ metadata: { analystRun: result },
313
+ ...options.failOnAnalystFailure ? { error: registryRunFailure(result) } : {}
118
314
  };
119
315
  }
120
316
  };
@@ -127,19 +323,27 @@ async function runBenchmarkJob(job, signal, resolveEvidence) {
127
323
  repetition: job.repetition,
128
324
  signal
129
325
  });
326
+ if (output.usage) assertValidAnalystUsageReceipt(output.usage, "analyst benchmark usage");
327
+ const benchmarkLatencyMs = performance.now() - started;
328
+ const latency = resolveBenchmarkLatency(output.observedLatencyMs, benchmarkLatencyMs);
329
+ const scoredFindings = output.error ? [] : output.findings;
130
330
  return {
131
331
  runnerId: job.runner.id,
132
332
  caseId: job.testCase.id,
333
+ clusterId: job.testCase.clusterId,
334
+ labelState: job.testCase.labelState,
133
335
  repetition: job.repetition,
134
336
  executionIndex: job.executionIndex,
135
- latencyMs: performance.now() - started,
337
+ latencyMs: latency.value,
338
+ latencySource: latency.source,
136
339
  findings: output.findings,
137
- score: scoreAnalystFindings(job.testCase, output.findings),
340
+ score: scoreAnalystFindings(job.testCase, scoredFindings),
138
341
  evidenceResolution: resolveEvidence ? await resolveFindingEvidence(job.testCase, output.findings, resolveEvidence, signal) : void 0,
139
342
  caseTags: [...job.testCase.tags ?? []],
140
343
  caseMetadata: job.testCase.metadata,
141
344
  usage: output.usage,
142
- runnerMetadata: output.metadata
345
+ runnerMetadata: output.metadata,
346
+ ...output.error ? { error: output.error } : {}
143
347
  };
144
348
  } catch (error) {
145
349
  if (signal?.aborted) throw error;
@@ -147,9 +351,12 @@ async function runBenchmarkJob(job, signal, resolveEvidence) {
147
351
  return {
148
352
  runnerId: job.runner.id,
149
353
  caseId: job.testCase.id,
354
+ clusterId: job.testCase.clusterId,
355
+ labelState: job.testCase.labelState,
150
356
  repetition: job.repetition,
151
357
  executionIndex: job.executionIndex,
152
358
  latencyMs: performance.now() - started,
359
+ latencySource: "benchmark-clock",
153
360
  findings,
154
361
  score: scoreAnalystFindings(job.testCase, findings),
155
362
  caseTags: [...job.testCase.tags ?? []],
@@ -161,6 +368,29 @@ async function runBenchmarkJob(job, signal, resolveEvidence) {
161
368
  };
162
369
  }
163
370
  }
371
+ function resolveBenchmarkLatency(observedLatencyMs, fallbackMs) {
372
+ if (observedLatencyMs === void 0) return {
373
+ value: fallbackMs,
374
+ source: "benchmark-clock"
375
+ };
376
+ if (observedLatencyMs === null) return {
377
+ value: null,
378
+ source: "uncaptured"
379
+ };
380
+ if (!Number.isFinite(observedLatencyMs) || observedLatencyMs < 0) throw new RangeError("analyst benchmark observedLatencyMs must be finite and non-negative");
381
+ return {
382
+ value: observedLatencyMs,
383
+ source: "runner-reported"
384
+ };
385
+ }
386
+ function registryRunFailure(result) {
387
+ const failed = result.per_analyst.filter((summary) => summary.status === "failed");
388
+ if (failed.length === 0) return void 0;
389
+ return {
390
+ class: "AnalystRunFailure",
391
+ message: failed.map((summary) => `${summary.analyst_id}: ${summary.error?.class ?? "Error"}: ${summary.error?.message ?? "analyst failed"}`).join("; ")
392
+ };
393
+ }
164
394
  function benchmarkJobs(cases, runners, repetitions, runnerOrderSeed) {
165
395
  const seededRunners = [...runners].sort((left, right) => stableHash(`${runnerOrderSeed}\u0000${left.id}`) - stableHash(`${runnerOrderSeed}\u0000${right.id}`) || left.id.localeCompare(right.id));
166
396
  let executionIndex = 0;
@@ -174,6 +404,35 @@ function benchmarkJobs(cases, runners, repetitions, runnerOrderSeed) {
174
404
  }));
175
405
  }).flat());
176
406
  }
407
+ function validateInitialObservations(observations, jobs) {
408
+ const expected = new Map(jobs.map((job) => [jobKey(job), job]));
409
+ const seen = /* @__PURE__ */ new Set();
410
+ return observations.map((observation) => {
411
+ const key = observationKey(observation);
412
+ if (seen.has(key)) throw new TypeError(`duplicate initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}'`);
413
+ seen.add(key);
414
+ const job = expected.get(key);
415
+ if (!job) throw new TypeError(`initial analyst benchmark observation does not match a planned job: '${observation.runnerId}/${observation.caseId}/${observation.repetition}'`);
416
+ if (observation.executionIndex !== job.executionIndex) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' has executionIndex ${observation.executionIndex}; expected ${job.executionIndex}`);
417
+ if (observation.clusterId !== job.testCase.clusterId || observation.labelState !== job.testCase.labelState) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' does not match the current case labels`);
418
+ if (JSON.stringify(observation.caseTags) !== JSON.stringify(job.testCase.tags ?? []) || JSON.stringify(observation.caseMetadata) !== JSON.stringify(job.testCase.metadata)) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' does not match the current case metadata`);
419
+ const expectedScore = scoreAnalystFindings(job.testCase, observation.error ? [] : observation.findings);
420
+ if (JSON.stringify(observation.score) !== JSON.stringify(expectedScore)) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' has stale or invalid scores`);
421
+ if (observation.usage) assertValidAnalystUsageReceipt(observation.usage, `initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' usage`);
422
+ if (![
423
+ "benchmark-clock",
424
+ "runner-reported",
425
+ "uncaptured"
426
+ ].includes(observation.latencySource) || observation.latencySource === "uncaptured" && observation.latencyMs !== null || observation.latencySource !== "uncaptured" && (observation.latencyMs === null || !Number.isFinite(observation.latencyMs) || observation.latencyMs < 0)) throw new TypeError(`initial analyst benchmark observation '${observation.runnerId}/${observation.caseId}/${observation.repetition}' has invalid latency`);
427
+ return { ...observation };
428
+ });
429
+ }
430
+ function jobKey(job) {
431
+ return `${job.runner.id}\u0000${job.testCase.id}\u0000${job.repetition}`;
432
+ }
433
+ function observationKey(observation) {
434
+ return `${observation.runnerId}\u0000${observation.caseId}\u0000${observation.repetition}`;
435
+ }
177
436
  async function resolveFindingEvidence(testCase, findings, resolver, signal) {
178
437
  const evidence = findings.flatMap((finding) => finding.evidence_refs);
179
438
  const resolved = [];
@@ -206,81 +465,6 @@ async function resolveFindingEvidence(testCase, findings, resolver, signal) {
206
465
  validity: evidence.length === 0 || errors.length > 0 ? null : resolved.length / evidence.length
207
466
  };
208
467
  }
209
- function summarizeRunner(runnerId, observations) {
210
- const issueBearing = observations.filter((observation) => observation.score.expectedIssueCount > 0);
211
- const expectedIssues = issueBearing.reduce((sum, observation) => sum + observation.score.expectedIssueCount, 0);
212
- const matchedIssues = issueBearing.reduce((sum, observation) => sum + observation.score.matchedIssueIds.length, 0);
213
- const issueFindings = issueBearing.reduce((sum, observation) => sum + observation.findings.length, 0);
214
- const supportedFindings = issueBearing.reduce((sum, observation) => sum + observation.score.supportedFindingIndexes.length, 0);
215
- const issueRecall = expectedIssues === 0 ? null : matchedIssues / expectedIssues;
216
- const findingPrecision = expectedIssues === 0 ? null : issueFindings === 0 ? 0 : supportedFindings / issueFindings;
217
- const critical = observations.map((observation) => observation.score.criticalStepAccuracy).filter((value) => value !== null);
218
- const findingsWithEvidence = observations.reduce((sum, observation) => sum + observation.findings.filter((finding) => finding.evidence_refs.length > 0).length, 0);
219
- const allFindings = observations.reduce((sum, observation) => sum + observation.findings.length, 0);
220
- const citationObservations = observations.filter((observation) => observation.score.citationLabelAgreement !== null);
221
- const citationCount = citationObservations.reduce((sum, observation) => sum + observation.findings.reduce((count, finding) => count + finding.evidence_refs.length, 0), 0);
222
- const invalidCitationCount = citationObservations.reduce((sum, observation) => sum + observation.score.unlabeledEvidence.length, 0);
223
- const clean = observations.filter((observation) => observation.score.expectedIssueCount === 0);
224
- const completedClean = clean.filter((observation) => !observation.error);
225
- const resolvedCitations = observations.reduce((sum, observation) => sum + (observation.evidenceResolution?.resolved ?? 0), 0);
226
- const unresolvedCitations = observations.reduce((sum, observation) => sum + (observation.evidenceResolution?.unresolvedEvidence.length ?? 0), 0);
227
- const citationResolutionErrors = observations.reduce((sum, observation) => sum + (observation.evidenceResolution?.errors.length ?? 0), 0);
228
- const resolutionAttempts = observations.filter((observation) => observation.findings.some((finding) => finding.evidence_refs.length > 0));
229
- const citationResolutionUnknownRuns = resolutionAttempts.filter((observation) => !observation.evidenceResolution || observation.evidenceResolution.errors.length > 0).length;
230
- const usages = observations.map((observation) => observation.usage);
231
- const knownCostUsd = usages.reduce((sum, usage) => {
232
- if (!usage) return sum;
233
- return sum + (usage.cost.kind === "uncaptured" ? usage.knownCostUsd ?? 0 : usage.cost.usd);
234
- }, 0);
235
- return {
236
- runnerId,
237
- plannedRuns: observations.length,
238
- completedRuns: observations.filter((observation) => !observation.error).length,
239
- failedRuns: observations.filter((observation) => Boolean(observation.error)).length,
240
- issueRecall,
241
- findingPrecision,
242
- f1: findingPrecision === null || issueRecall === null ? null : harmonicMean(findingPrecision, issueRecall),
243
- criticalStepAccuracy: critical.length === 0 ? null : mean(critical),
244
- citationCoverage: allFindings === 0 ? null : findingsWithEvidence / allFindings,
245
- citationLabelAgreement: citationObservations.length === 0 ? null : citationCount === 0 ? 0 : (citationCount - invalidCitationCount) / citationCount,
246
- citationResolution: resolutionAttempts.length === 0 || citationResolutionUnknownRuns > 0 || resolvedCitations + unresolvedCitations === 0 ? null : resolvedCitations / (resolvedCitations + unresolvedCitations),
247
- citationResolutionUnknownRuns,
248
- unresolvedCitations,
249
- citationResolutionErrors,
250
- cleanCaseFalsePositiveRate: completedClean.length === 0 ? null : completedClean.filter((observation) => observation.score.cleanFalsePositive).length / completedClean.length,
251
- cleanCaseFailureRate: clean.length === 0 ? null : clean.filter((observation) => Boolean(observation.error)).length / clean.length,
252
- runAgreement: issueAgreement(observations.filter((observation) => !observation.error)),
253
- latencyMs: latencyDistribution(observations.map((observation) => observation.latencyMs)),
254
- calls: usages.reduce((sum, usage) => sum + (usage?.calls ?? 0), 0),
255
- callsUnknownRuns: usages.filter((usage) => !usage || usage.calls === null).length,
256
- inputTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.input ?? 0), 0),
257
- outputTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.output ?? 0), 0),
258
- reasoningTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.reasoning ?? 0), 0),
259
- cachedTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.cached ?? 0), 0),
260
- cacheWriteTokens: usages.reduce((sum, usage) => sum + (usage?.tokens?.cacheWrite ?? 0), 0),
261
- tokenUsageUnknownRuns: usages.filter((usage) => !usage?.tokens).length,
262
- reasoningTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.reasoning === void 0).length,
263
- cachedTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.cached === void 0).length,
264
- cacheWriteTokenUsageUnknownRuns: usages.filter((usage) => usage?.tokens?.cacheWrite === void 0).length,
265
- knownCostUsd,
266
- costUnknownRuns: usages.filter((usage) => !usage || usage.cost.kind === "uncaptured").length
267
- };
268
- }
269
- function findingMatchesIssue(finding, issue) {
270
- if (issue.findingIds && !issue.findingIds.includes(finding.finding_id)) return false;
271
- if (issue.areas && !issue.areas.includes(finding.area)) return false;
272
- if (issue.subjects && (!finding.subject || !issue.subjects.includes(finding.subject))) return false;
273
- if (issue.evidence && !matchesEvidence(finding.evidence_refs, issue.evidence, issue.evidenceMode ?? "any")) return false;
274
- return true;
275
- }
276
- function matchesEvidence(actual, expected, mode) {
277
- if (expected.length === 0) return true;
278
- const match = (target) => actual.some((ref) => evidenceMatches(ref, target));
279
- return mode === "all" ? expected.every(match) : expected.some(match);
280
- }
281
- function evidenceMatches(actual, expected) {
282
- return actual.uri === expected.uri && (expected.kind === void 0 || actual.kind === expected.kind);
283
- }
284
468
  function parseTraceSpanUri(uri) {
285
469
  const match = /^trace:\/\/([^/]+)\/span\/([^/]+)$/.exec(uri);
286
470
  if (!match) return null;
@@ -295,17 +479,6 @@ function parseTraceSpanUri(uri) {
295
479
  return null;
296
480
  }
297
481
  }
298
- function validateCase(testCase) {
299
- if (!testCase.id.trim()) throw new TypeError("analyst benchmark case id must not be empty");
300
- const ids = /* @__PURE__ */ new Set();
301
- for (const issue of testCase.expectedIssues) {
302
- if (!issue.id.trim()) throw new TypeError(`${testCase.id}: expected issue id must not be empty`);
303
- if (ids.has(issue.id)) throw new TypeError(`${testCase.id}: duplicate expected issue id '${issue.id}'`);
304
- ids.add(issue.id);
305
- if (!issue.findingIds?.length && !issue.areas?.length && !issue.subjects?.length && !issue.evidence?.length) throw new TypeError(`${testCase.id}/${issue.id}: expected issue must identify a finding by id, area, subject, or evidence`);
306
- }
307
- for (const ref of testCase.labeledEvidence ?? []) if (!ref.uri.trim()) throw new TypeError(`${testCase.id}: labeled evidence URI must not be empty`);
308
- }
309
482
  function validateBenchmarkOptions(options) {
310
483
  if (options.cases.length === 0) throw new TypeError("runAnalystBenchmark requires cases");
311
484
  if (options.runners.length === 0) throw new TypeError("runAnalystBenchmark requires runners");
@@ -316,7 +489,14 @@ function validateBenchmarkOptions(options) {
316
489
  if (!Number.isSafeInteger(options.runnerOrderSeed ?? 0)) throw new RangeError("runAnalystBenchmark runnerOrderSeed must be a safe integer");
317
490
  assertUniqueNonEmpty(options.cases.map((testCase) => testCase.id), "case");
318
491
  assertUniqueNonEmpty(options.runners.map((runner) => runner.id), "runner");
319
- for (const testCase of options.cases) validateCase(testCase);
492
+ for (const testCase of options.cases) validateBenchmarkCase(testCase);
493
+ }
494
+ function validateBenchmarkCase(testCase) {
495
+ assertValidAnalystScoringCase(testCase);
496
+ if (!testCase.clusterId.trim()) throw new TypeError(`${testCase.id}: analyst benchmark clusterId must not be empty`);
497
+ if (testCase.labelState !== "positive" && testCase.labelState !== "trusted-negative" && testCase.labelState !== "unlabeled") throw new TypeError(`${testCase.id}: analyst benchmark labelState is invalid`);
498
+ if (testCase.labelState === "positive" && testCase.expectedIssues.length === 0) throw new TypeError(`${testCase.id}: positive case requires at least one expected issue`);
499
+ if (testCase.labelState !== "positive" && testCase.expectedIssues.length > 0) throw new TypeError(`${testCase.id}: ${testCase.labelState} case cannot contain expected issues`);
320
500
  }
321
501
  function assertUniqueNonEmpty(values, label) {
322
502
  const seen = /* @__PURE__ */ new Set();
@@ -326,26 +506,6 @@ function assertUniqueNonEmpty(values, label) {
326
506
  seen.add(value);
327
507
  }
328
508
  }
329
- function harmonicMean(a, b) {
330
- return a + b === 0 ? 0 : 2 * a * b / (a + b);
331
- }
332
- function mean(values) {
333
- return values.length === 0 ? 0 : values.reduce((sum, value) => sum + value, 0) / values.length;
334
- }
335
- function latencyDistribution(values) {
336
- const sorted = [...values].sort((a, b) => a - b);
337
- return {
338
- min: sorted[0] ?? 0,
339
- mean: mean(sorted),
340
- p50: percentile(sorted, .5),
341
- p95: percentile(sorted, .95),
342
- max: sorted.at(-1) ?? 0
343
- };
344
- }
345
- function percentile(sorted, quantile) {
346
- if (sorted.length === 0) return 0;
347
- return sorted[Math.ceil(quantile * sorted.length) - 1] ?? sorted.at(-1) ?? 0;
348
- }
349
509
  function stableHash(value) {
350
510
  let hash = 2166136261;
351
511
  for (let index = 0; index < value.length; index += 1) {
@@ -354,26 +514,6 @@ function stableHash(value) {
354
514
  }
355
515
  return hash >>> 0;
356
516
  }
357
- function issueAgreement(observations) {
358
- const byCase = /* @__PURE__ */ new Map();
359
- for (const observation of observations) {
360
- const rows = byCase.get(observation.caseId) ?? [];
361
- rows.push(observation);
362
- byCase.set(observation.caseId, rows);
363
- }
364
- const agreements = [];
365
- for (const rows of byCase.values()) for (let left = 0; left < rows.length; left++) for (let right = left + 1; right < rows.length; right++) agreements.push(jaccard(rows[left].score.matchedIssueIds, rows[right].score.matchedIssueIds));
366
- return agreements.length === 0 ? null : mean(agreements);
367
- }
368
- function jaccard(left, right) {
369
- const a = new Set(left);
370
- const b = new Set(right);
371
- const union = /* @__PURE__ */ new Set([...a, ...b]);
372
- if (union.size === 0) return 1;
373
- let intersection = 0;
374
- for (const value of a) if (b.has(value)) intersection += 1;
375
- return intersection / union.size;
376
- }
377
517
  function mergeRegistryUsage(result) {
378
518
  const usages = result.per_analyst.map((summary) => summary.usage);
379
519
  const calls = usages.every((usage) => usage.calls !== null) ? usages.reduce((sum, usage) => sum + (usage.calls ?? 0), 0) : null;
@@ -409,6 +549,6 @@ function mergeRegistryUsage(result) {
409
549
  };
410
550
  }
411
551
  //#endregion
412
- export { traceStoreEvidenceResolver as i, runAnalystBenchmark as n, scoreAnalystFindings as r, registryBenchmarkRunner as t };
552
+ export { scoreAnalystFindings as a, summarizeAnalystBenchmarkRunner as i, runAnalystBenchmark as n, traceStoreEvidenceResolver as r, registryBenchmarkRunner as t };
413
553
 
414
- //# sourceMappingURL=benchmark-YDrpumqB.js.map
554
+ //# sourceMappingURL=benchmark-CYtcIF2V.js.map