@tangle-network/agent-eval 0.189.0 → 0.190.1

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 (146) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +3 -2
  3. package/dist/adapters/http.d.ts +10 -1
  4. package/dist/adapters/http.d.ts.map +1 -1
  5. package/dist/adapters/http.js +75 -7
  6. package/dist/adapters/http.js.map +1 -1
  7. package/dist/analyst/index.d.ts +66 -3
  8. package/dist/analyst/index.d.ts.map +1 -1
  9. package/dist/analyst/index.js +212 -7
  10. package/dist/analyst/index.js.map +1 -1
  11. package/dist/{benchmark-command-BhhAYstU.js → benchmark-command-BXIr_k8D.js} +12 -11
  12. package/dist/benchmark-command-BXIr_k8D.js.map +1 -0
  13. package/dist/benchmarks/index.d.ts +1 -1
  14. package/dist/benchmarks/index.js +3 -3
  15. package/dist/campaign/index.d.ts +4 -4
  16. package/dist/campaign/index.js +7 -7
  17. package/dist/{campaign-BWoSQPSU.js → campaign-B7q74uIh.js} +256 -11
  18. package/dist/campaign-B7q74uIh.js.map +1 -0
  19. package/dist/{campaign-evidence-CBlGJ3kC.js → campaign-evidence-D40-LHIL.js} +6 -5
  20. package/dist/campaign-evidence-D40-LHIL.js.map +1 -0
  21. package/dist/{chat-client-C4dg7Ng_.js → chat-client-B_ItsSBw.js} +2 -2
  22. package/dist/{chat-client-C4dg7Ng_.js.map → chat-client-B_ItsSBw.js.map} +1 -1
  23. package/dist/{chat-json-call-JVGbfx95.js → chat-json-call-CXHBvUz8.js} +2 -2
  24. package/dist/{chat-json-call-JVGbfx95.js.map → chat-json-call-CXHBvUz8.js.map} +1 -1
  25. package/dist/claim-BGOUqoMq.d.ts +38 -0
  26. package/dist/claim-BGOUqoMq.d.ts.map +1 -0
  27. package/dist/cli.js +3 -3
  28. package/dist/contract/index.d.ts +4 -4
  29. package/dist/contract/index.js +8 -8
  30. package/dist/{default-registry-m2jhbG6Q.js → default-registry-16I-Bxlf.js} +2 -2
  31. package/dist/{default-registry-m2jhbG6Q.js.map → default-registry-16I-Bxlf.js.map} +1 -1
  32. package/dist/{define-agent-eval-98sgUaC1.js → define-agent-eval-DPizhHfy.js} +4 -4
  33. package/dist/{define-agent-eval-98sgUaC1.js.map → define-agent-eval-DPizhHfy.js.map} +1 -1
  34. package/dist/{define-agent-eval-q6890HU4.d.ts → define-agent-eval-DblGedQk.d.ts} +4 -3
  35. package/dist/define-agent-eval-DblGedQk.d.ts.map +1 -0
  36. package/dist/diagnosis/index.d.ts +14 -2
  37. package/dist/diagnosis/index.d.ts.map +1 -1
  38. package/dist/diagnosis/index.js +11 -4
  39. package/dist/diagnosis/index.js.map +1 -1
  40. package/dist/{dspy-rlm-engine-DLYttKBm.js → dspy-rlm-engine-Cs5LzJ-f.js} +3 -3
  41. package/dist/{dspy-rlm-engine-DLYttKBm.js.map → dspy-rlm-engine-Cs5LzJ-f.js.map} +1 -1
  42. package/dist/{eval-campaign-C0Q1Eeo_.js → eval-campaign-CwsBn0Gm.js} +2 -2
  43. package/dist/{eval-campaign-C0Q1Eeo_.js.map → eval-campaign-CwsBn0Gm.js.map} +1 -1
  44. package/dist/experiment/index.d.ts +2 -1
  45. package/dist/experiment/index.d.ts.map +1 -1
  46. package/dist/experiment/index.js +1 -1
  47. package/dist/exporters-XMCYJlqT.d.ts +161 -0
  48. package/dist/exporters-XMCYJlqT.d.ts.map +1 -0
  49. package/dist/{external-optimizer-process-C3quNeKV.js → external-optimizer-process-BnSmrkgL.js} +3 -3
  50. package/dist/{external-optimizer-process-C3quNeKV.js.map → external-optimizer-process-BnSmrkgL.js.map} +1 -1
  51. package/dist/{external-optimizer-subprocess-BDwgKLO1.js → external-optimizer-subprocess-kbcPu5po.js} +3 -3
  52. package/dist/{external-optimizer-subprocess-BDwgKLO1.js.map → external-optimizer-subprocess-kbcPu5po.js.map} +1 -1
  53. package/dist/{feedback-trajectory-B3ZSkfAq.d.ts → feedback-trajectory-BYjh7NEM.d.ts} +2 -2
  54. package/dist/{feedback-trajectory-B3ZSkfAq.d.ts.map → feedback-trajectory-BYjh7NEM.d.ts.map} +1 -1
  55. package/dist/{index-CnJOPR_E.d.ts → index-CJv56rhX.d.ts} +673 -244
  56. package/dist/index-CJv56rhX.d.ts.map +1 -0
  57. package/dist/{index-Dk1dOUfm.d.ts → index-CWoIJHmy.d.ts} +4 -4
  58. package/dist/{index-Dk1dOUfm.d.ts.map → index-CWoIJHmy.d.ts.map} +1 -1
  59. package/dist/{index-Bcc9PkOz.d.ts → index-D4pC7Tvd.d.ts} +3 -2
  60. package/dist/index-D4pC7Tvd.d.ts.map +1 -0
  61. package/dist/{index-BqDJ6fUK.d.ts → index-I4UJGRno.d.ts} +74 -2
  62. package/dist/index-I4UJGRno.d.ts.map +1 -0
  63. package/dist/index.d.ts +66 -17
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +375 -31
  66. package/dist/index.js.map +1 -1
  67. package/dist/{journal-CDP0Vhx_.js → journal-c3qY0SDP.js} +246 -5
  68. package/dist/journal-c3qY0SDP.js.map +1 -0
  69. package/dist/{kind-factory-BGwN0nE6.js → kind-factory-ByZeXDqF.js} +2 -2
  70. package/dist/{kind-factory-BGwN0nE6.js.map → kind-factory-ByZeXDqF.js.map} +1 -1
  71. package/dist/ledger-core/index.d.ts +2 -2
  72. package/dist/ledger-core/index.js +2 -2
  73. package/dist/{llm-client-DF9bPNYA.js → llm-client-KYQBh9C8.js} +2 -2
  74. package/dist/{llm-client-DF9bPNYA.js.map → llm-client-KYQBh9C8.js.map} +1 -1
  75. package/dist/{llm-judge-M_l_WXw2.js → llm-judge-BnxAv7ed.js} +1892 -1378
  76. package/dist/llm-judge-BnxAv7ed.js.map +1 -0
  77. package/dist/mint-C9Lojt1V.d.ts +147 -0
  78. package/dist/mint-C9Lojt1V.d.ts.map +1 -0
  79. package/dist/openapi.json +1 -1
  80. package/dist/pipelines/index.d.ts +1 -1
  81. package/dist/pipelines/index.js +1 -1
  82. package/dist/pipelines/index.js.map +1 -1
  83. package/dist/{produced-state-BOY98IJO.js → produced-state-BXjXJHJT.js} +4 -4
  84. package/dist/{produced-state-BOY98IJO.js.map → produced-state-BXjXJHJT.js.map} +1 -1
  85. package/dist/{raw-provider-sink-C96CcqeG.js → raw-provider-sink-DYh4Bszw.js} +2 -2
  86. package/dist/{raw-provider-sink-C96CcqeG.js.map → raw-provider-sink-DYh4Bszw.js.map} +1 -1
  87. package/dist/{redact-DxwlUAvh.js → redact-B7rt9Zot.js} +130 -10
  88. package/dist/redact-B7rt9Zot.js.map +1 -0
  89. package/dist/{redact-DPoH1-OW.d.ts → redact-CopuMxll.d.ts} +7 -5
  90. package/dist/redact-CopuMxll.d.ts.map +1 -0
  91. package/dist/rl.d.ts +3 -2
  92. package/dist/rl.d.ts.map +1 -1
  93. package/dist/rl.js +1 -1
  94. package/dist/rollout/index.d.ts +3 -2
  95. package/dist/search-ledger-types-C9anU2DX.d.ts +489 -0
  96. package/dist/search-ledger-types-C9anU2DX.d.ts.map +1 -0
  97. package/dist/{semantic-concept-judge-Cm2K2wAo.js → semantic-concept-judge-ClB73cei.js} +6 -6
  98. package/dist/{semantic-concept-judge-Cm2K2wAo.js.map → semantic-concept-judge-ClB73cei.js.map} +1 -1
  99. package/dist/{server-C8XvqCg8.js → server-DtetGGoA.js} +2 -2
  100. package/dist/{server-C8XvqCg8.js.map → server-DtetGGoA.js.map} +1 -1
  101. package/dist/{skillopt-optimization-method-BVSB676o.js → skillopt-optimization-method-l2vDBtKp.js} +363 -18
  102. package/dist/skillopt-optimization-method-l2vDBtKp.js.map +1 -0
  103. package/dist/{spans-W0o8pbEg.d.ts → spans-BWtjk_9c.d.ts} +2 -2
  104. package/dist/{spans-W0o8pbEg.d.ts.map → spans-BWtjk_9c.d.ts.map} +1 -1
  105. package/dist/{statistical-heldout-plAHG82H.d.ts → statistical-heldout-CLpjYffJ.d.ts} +2 -36
  106. package/dist/statistical-heldout-CLpjYffJ.d.ts.map +1 -0
  107. package/dist/{store-otlp-D0AV4cvB.js → store-otlp-84DH0OEH.js} +2 -2
  108. package/dist/{store-otlp-D0AV4cvB.js.map → store-otlp-84DH0OEH.js.map} +1 -1
  109. package/dist/{store-tool-spans-8PAyhRqb.d.ts → store-tool-spans-BBXs8hDP.d.ts} +2 -2
  110. package/dist/{store-tool-spans-8PAyhRqb.d.ts.map → store-tool-spans-BBXs8hDP.d.ts.map} +1 -1
  111. package/dist/{store-tool-spans-B_0mXvYl.js → store-tool-spans-DUiqyHvR.js} +5 -5
  112. package/dist/{store-tool-spans-B_0mXvYl.js.map → store-tool-spans-DUiqyHvR.js.map} +1 -1
  113. package/dist/traces.d.ts +3 -63
  114. package/dist/traces.d.ts.map +1 -1
  115. package/dist/traces.js +6 -195
  116. package/dist/traces.js.map +1 -1
  117. package/dist/wire/index.d.ts +1 -1
  118. package/dist/wire/index.js +1 -1
  119. package/docs/adapters-observability.md +31 -27
  120. package/docs/charter.md +1 -1
  121. package/docs/design/mlbenchmarks-book-review.md +1 -1
  122. package/docs/distributed-driver.md +1 -5
  123. package/docs/experiment.md +15 -0
  124. package/docs/public-api.md +181 -71
  125. package/docs/redaction.md +7 -1
  126. package/docs/search-ledger.md +174 -0
  127. package/docs/trace-contracts.md +48 -3
  128. package/package.json +1 -1
  129. package/dist/benchmark-command-BhhAYstU.js.map +0 -1
  130. package/dist/campaign-BWoSQPSU.js.map +0 -1
  131. package/dist/campaign-evidence-CBlGJ3kC.js.map +0 -1
  132. package/dist/define-agent-eval-q6890HU4.d.ts.map +0 -1
  133. package/dist/index-Bcc9PkOz.d.ts.map +0 -1
  134. package/dist/index-BqDJ6fUK.d.ts.map +0 -1
  135. package/dist/index-CnJOPR_E.d.ts.map +0 -1
  136. package/dist/journal-CDP0Vhx_.js.map +0 -1
  137. package/dist/llm-judge-M_l_WXw2.js.map +0 -1
  138. package/dist/mint-DsS38v5c.d.ts +0 -305
  139. package/dist/mint-DsS38v5c.d.ts.map +0 -1
  140. package/dist/redact-DPoH1-OW.d.ts.map +0 -1
  141. package/dist/redact-DxwlUAvh.js.map +0 -1
  142. package/dist/search-ledger-types-D7KDVg-o.d.ts +0 -302
  143. package/dist/search-ledger-types-D7KDVg-o.d.ts.map +0 -1
  144. package/dist/skillopt-optimization-method-BVSB676o.js.map +0 -1
  145. package/dist/statistical-heldout-plAHG82H.d.ts.map +0 -1
  146. package/docs/search-history-receipts.md +0 -233
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.js";
2
2
  import { i as JudgeError, o as NotFoundError, r as ConfigError, s as ValidationError, t as AgentEvalError } from "./errors-Dngq5h35.js";
3
3
  import { a as hashCanonical, r as canonicalString } from "./canonical-BsZVgBhW.js";
4
- import { a as CODING_HARNESSES, c as agentProfileId, d as harnessAxisOf, i as verifyCompletion, l as agentProfileModelId, n as completionVerdict, o as HARNESS_NATIVE_MODEL, r as createLlmCorrectnessChecker, s as agentProfileHash, t as extractProducedState, u as expandProfileAxes } from "./produced-state-BOY98IJO.js";
4
+ import { a as CODING_HARNESSES, c as agentProfileId, d as harnessAxisOf, i as verifyCompletion, l as agentProfileModelId, n as completionVerdict, o as HARNESS_NATIVE_MODEL, r as createLlmCorrectnessChecker, s as agentProfileHash, t as extractProducedState, u as expandProfileAxes } from "./produced-state-BXjXJHJT.js";
5
5
  import { c as groupRunsByAgentProfileCell, d as validateAgentProfileCell, f as verifyAgentProfileCell, h as manifestContentDigest, i as agentProfileCellKey, m as hashJson, r as agentProfileCellHashMaterial, s as buildAgentProfileCell, t as AGENT_PROFILE_KINDS, u as toAgentProfileJson } from "./agent-profile-cell-DcFW4vOc.js";
6
6
  import { t as mulberry32 } from "./random-Dn5fPWkt.js";
7
7
  import { a as spearmanR, c as weightedMean, i as ranks, n as partialCredit, o as summarizeNumberSeries, r as pearsonR, s as weightedComposite, t as confidenceInterval } from "./descriptive-1V17A-qa.js";
@@ -21,40 +21,40 @@ import { r as observedSplitScore, t as isRealnessGated } from "./reward-nw2xZGZG
21
21
  import { a as parseRunRecordSafe, c as runTaskScore, i as modelHasSnapshot, l as searchCellRunId, n as UNKNOWN_MODEL, o as roundTripRunRecord, r as isRunRecord, s as runCostFloorUsd, t as RunRecordValidationError, u as validateRunRecord } from "./run-record-BRlM99rX.js";
22
22
  import { a as summaryTable, n as gainHistogram, r as paretoChart } from "./summary-report-BMvou2oZ.js";
23
23
  import { n as contentHash, r as fileVerdictCache, t as canonicalJson } from "./verdict-cache-BQEsVy9n.js";
24
- import { B as DEFAULT_RED_TEAM_CORPUS, H as redTeamReport, N as dominates, P as paretoFrontier, U as scoreRedTeamOutput, V as redTeamDataset, W as runCanaries, X as runCampaign, a as transientDispatchFailure, i as quotaExhaustedUntil, o as aggregateRunScore, s as clamp01, t as llmJudge } from "./llm-judge-M_l_WXw2.js";
24
+ import { $ as scoreRedTeamOutput, Q as redTeamReport, U as dominates, W as paretoFrontier, X as DEFAULT_RED_TEAM_CORPUS, Z as redTeamDataset, a as transientDispatchFailure, et as runCanaries, i as quotaExhaustedUntil, o as aggregateRunScore, ot as runCampaign, s as clamp01, t as llmJudge } from "./llm-judge-BnxAv7ed.js";
25
25
  import { a as resolveModelPricing, i as isModelPriced, n as estimateCost, r as estimateTokens, t as MODEL_PRICING } from "./metrics-DOMpkoeb.js";
26
26
  import { a as CostLedgerPersistenceError, c as costForTokenPricing, i as CostLedger, l as costForUsage, n as CostCallConflictError, o as CostReceiptCaptureError, r as CostCeilingReachedError, s as CostReservationExceededError, t as CostAccountingIncompleteError, u as modelPriceKey } from "./cost-ledger-_IbkejCF.js";
27
- import { D as parseReflectionResponse, E as buildReflectionPrompt, G as summarizeBackendIntegrity, U as BackendIntegrityError, W as assertRealBackend, c as ATTESTATION_ALGORITHM, l as attest, u as verifyAttestation, z as surfaceContentHash } from "./campaign-evidence-CBlGJ3kC.js";
28
- import { d as redactForShare, f as redactText, n as REDACTION_VERSION, p as shareAllowed, r as assessShareSafety, u as redact } from "./redact-DxwlUAvh.js";
29
- import { r as selfImprove, t as defineAgentEval } from "./define-agent-eval-98sgUaC1.js";
27
+ import { B as surfaceContentHash, D as parseReflectionResponse, E as buildReflectionPrompt, G as assertRealBackend, K as summarizeBackendIntegrity, W as BackendIntegrityError, c as ATTESTATION_ALGORITHM, l as attest, u as verifyAttestation } from "./campaign-evidence-D40-LHIL.js";
28
+ import { d as redactForShare, f as redactText, n as REDACTION_VERSION, p as shareAllowed, r as assessShareSafety, u as redact } from "./redact-B7rt9Zot.js";
29
+ import { r as selfImprove, t as defineAgentEval } from "./define-agent-eval-DPizhHfy.js";
30
30
  import { n as equivalenceVerdict, t as certificationEvidenceDigest } from "./verdict-DhPoXApk.js";
31
31
  import { t as TraceEmitter } from "./emitter-DbMXUIVP.js";
32
32
  import { t as buildTrajectory } from "./trajectory-D_7rLrvE.js";
33
33
  import { t as runCounterfactual } from "./counterfactual-DtMWvn7D.js";
34
- import { H as parseFindingSubject, W as DEFAULT_TRACE_ANALYST_OUTPUT_TOKENS, h as TRACE_ANALYST_TRUNCATION_MARKER_PREFIX, m as DEFAULT_TRACE_ANALYST_BUDGETS, t as createTraceAnalyst, u as createBoundedTraceAnalysisStore } from "./kind-factory-BGwN0nE6.js";
35
- import { _ as completedAnalystReviewQuality, b as validateAnalystReviewDecisions, c as FAILURE_MODE_KIND_SPEC, g as assertUniqueFindingIds, h as analystRunDigest, i as DEFAULT_TRACE_ANALYST_KINDS, m as analystFindingDigest, n as AnalystRegistry, t as buildDefaultAnalystRegistry, v as readAnalystReview, y as snapshotAnalystRun } from "./default-registry-m2jhbG6Q.js";
34
+ import { G as DEFAULT_TRACE_ANALYST_OUTPUT_TOKENS, U as parseFindingSubject, h as TRACE_ANALYST_TRUNCATION_MARKER_PREFIX, m as DEFAULT_TRACE_ANALYST_BUDGETS, t as createTraceAnalyst, u as createBoundedTraceAnalysisStore } from "./kind-factory-ByZeXDqF.js";
35
+ import { _ as completedAnalystReviewQuality, b as validateAnalystReviewDecisions, c as FAILURE_MODE_KIND_SPEC, g as assertUniqueFindingIds, h as analystRunDigest, i as DEFAULT_TRACE_ANALYST_KINDS, m as analystFindingDigest, n as AnalystRegistry, t as buildDefaultAnalystRegistry, v as readAnalystReview, y as snapshotAnalystRun } from "./default-registry-16I-Bxlf.js";
36
36
  import { OUTPUT_VALUE } from "./trace-attributes.js";
37
37
  import { r as extractUsageFromSse, t as extractUsage } from "./extract-usage-BrQ8mCLX.js";
38
- import { n as InMemoryRawProviderSink, r as NoopRawProviderSink, t as FileSystemRawProviderSink } from "./raw-provider-sink-C96CcqeG.js";
39
- import { D as captureFetchToRawSink, S as exportRunAsOtlp, _ as tokenizeDomainWords, d as inferDomainKeywords, g as scoreTraceInsightReadiness, l as describeTraceInsightScope, n as toolSpansToTraceAnalysisStore, o as buildTraceInsightContext, s as buildTraceInsightPrompt, u as domainEvidencePattern, v as analyzeTraces } from "./store-tool-spans-B_0mXvYl.js";
38
+ import { n as InMemoryRawProviderSink, r as NoopRawProviderSink, t as FileSystemRawProviderSink } from "./raw-provider-sink-DYh4Bszw.js";
39
+ import { S as exportRunAsOtlp, T as captureFetchToRawSink, _ as tokenizeDomainWords, d as inferDomainKeywords, g as scoreTraceInsightReadiness, l as describeTraceInsightScope, n as toolSpansToTraceAnalysisStore, o as buildTraceInsightContext, s as buildTraceInsightPrompt, u as domainEvidencePattern, v as analyzeTraces } from "./store-tool-spans-DUiqyHvR.js";
40
40
  import { n as assertRunCaptured, t as RunIntegrityError } from "./integrity-BFP4kjGj.js";
41
41
  import { n as InMemoryTraceStore, t as FileSystemTraceStore } from "./store-DNe_Uv1Q.js";
42
42
  import { t as packageVersion$1 } from "./package-version-D7lQHt_-.js";
43
43
  import { a as classifyFailure, i as INFRA_FAILURE_BLAMES, o as classifyFailureReason, r as FAILURE_BLAME, s as failureBlame, t as DEFAULT_FAILURE_REASON_RULES } from "./failure-taxonomy-CjabTATj.js";
44
44
  import { n as runBoundedProcess, t as DEFAULT_BOUNDED_PROCESS_TIMEOUT_MS } from "./bounded-process-BBZob7vl.js";
45
- import { t as runEvalCampaign } from "./eval-campaign-C0Q1Eeo_.js";
45
+ import { t as runEvalCampaign } from "./eval-campaign-CwsBn0Gm.js";
46
46
  import "./rollout-B-XBReTC.js";
47
47
  import { t as mintRolloutRows } from "./mint-iCD-W43s.js";
48
48
  import { n as pairedEvalueSequence, t as evaluateInterimReleaseConfidence } from "./sequential-CzK5DarL.js";
49
- import { S as judgeFamily, _ as assertServedModels, b as CrossFamilyError, d as stripFencedJson, f as ModelSubstitutionError, h as assertServedModel, i as backoffMs, l as isTransientLlmError, m as assertCrossFamilyServed, o as costReceiptFromLlm, p as ServedCrossFamilyError, r as LlmResponseError, s as costReceiptFromLlmError, t as LlmCallError, u as maximumChargeForLlmRequest, v as checkServedModel, x as assertCrossFamily, y as servedModelAcceptable } from "./llm-client-DF9bPNYA.js";
50
- import { n as paidJsonChat } from "./chat-json-call-JVGbfx95.js";
49
+ import { S as judgeFamily, _ as assertServedModels, b as CrossFamilyError, d as stripFencedJson, f as ModelSubstitutionError, h as assertServedModel, i as backoffMs, l as isTransientLlmError, m as assertCrossFamilyServed, o as costReceiptFromLlm, p as ServedCrossFamilyError, r as LlmResponseError, s as costReceiptFromLlmError, t as LlmCallError, u as maximumChargeForLlmRequest, v as checkServedModel, x as assertCrossFamily, y as servedModelAcceptable } from "./llm-client-KYQBh9C8.js";
50
+ import { n as paidJsonChat } from "./chat-json-call-CXHBvUz8.js";
51
51
  import { i as makeProposalFinding, n as computeFindingId, r as makeFinding } from "./types-CW5b90JH.js";
52
- import { t as createDspyRlmTraceEngine } from "./dspy-rlm-engine-DLYttKBm.js";
53
- import { a as diffFindings, n as runSemanticConceptJudge, o as createChatTraceEngine, r as FindingsStore, t as SEMANTIC_CONCEPT_JUDGE_VERSION } from "./semantic-concept-judge-Cm2K2wAo.js";
52
+ import { t as createDspyRlmTraceEngine } from "./dspy-rlm-engine-Cs5LzJ-f.js";
53
+ import { a as diffFindings, n as runSemanticConceptJudge, o as createChatTraceEngine, r as FindingsStore, t as SEMANTIC_CONCEPT_JUDGE_VERSION } from "./semantic-concept-judge-ClB73cei.js";
54
54
  import { t as analyzeSeries } from "./series-convergence-CjO2QdRW.js";
55
- import { i as otlpTextToTraceAnalysisStore, n as OtlpFileTraceStore } from "./store-otlp-D0AV4cvB.js";
55
+ import { i as otlpTextToTraceAnalysisStore, n as OtlpFileTraceStore } from "./store-otlp-84DH0OEH.js";
56
56
  import { n as evaluateReleaseConfidence, r as bootstrapCi } from "./release-confidence-XQm_xjhR.js";
57
- import { t as createChatClient } from "./chat-client-C4dg7Ng_.js";
57
+ import { t as createChatClient } from "./chat-client-B_ItsSBw.js";
58
58
  import { createHash } from "node:crypto";
59
59
  import { accessSync, appendFileSync, constants, cpSync, existsSync, mkdirSync, promises, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
60
60
  import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
@@ -1206,7 +1206,7 @@ function contractSpanKind(span) {
1206
1206
  /**
1207
1207
  * Tool name: `span.toolName` (typed ToolSpan) → the tool-name attributes
1208
1208
  * (`gen_ai.tool.name`, `tool.name`, ..., `toolName`) → `span.name` when the
1209
- * span is a TOOL span (otel-bridge's `ExportableSpan` drops `toolName`).
1209
+ * span is a TOOL span.
1210
1210
  */
1211
1211
  function contractSpanToolName(span) {
1212
1212
  if (typeof span.toolName === "string") return span.toolName;
@@ -1219,12 +1219,23 @@ function spanModel(span) {
1219
1219
  if (typeof span.model === "string" && span.model.length > 0) return span.model;
1220
1220
  return firstStringAttr(span.attributes ?? {}, MODEL_ATTR_KEYS);
1221
1221
  }
1222
- /** Input plus output tokens, or `undefined` when either side is unrecorded. */
1222
+ /**
1223
+ * Input plus output tokens, or `undefined` when either side is unrecorded.
1224
+ * Input follows the OTel GenAI definition — prompt tokens plus any cache
1225
+ * read and cache write tokens the span records — because a cached call's
1226
+ * `INPUT_TOKEN_ATTR_KEYS` value (`llm.token_count.prompt` and its aliases)
1227
+ * carries only the uncached remainder, and a cache-heavy call can spend far
1228
+ * more on cache reads and writes than on that remainder. `llm.maxTotalTokens`
1229
+ * would otherwise pass runs at a small fraction of what they actually cost.
1230
+ */
1223
1231
  function spanTotalTokens(span) {
1224
1232
  const attributes = span.attributes ?? {};
1225
1233
  const input = finite(span.inputTokens) ?? firstNumberAttr(attributes, INPUT_TOKEN_ATTR_KEYS);
1226
1234
  const output = finite(span.outputTokens) ?? firstNumberAttr(attributes, OUTPUT_TOKEN_ATTR_KEYS);
1227
- return input === void 0 || output === void 0 ? void 0 : input + output;
1235
+ if (input === void 0 || output === void 0) return void 0;
1236
+ const cacheRead = firstNumberAttr(attributes, ["llm.token_count.prompt_cache_hit"]) ?? 0;
1237
+ const cacheWrite = firstNumberAttr(attributes, ["llm.token_count.prompt_cache_write"]) ?? 0;
1238
+ return input + output + cacheRead + cacheWrite;
1228
1239
  }
1229
1240
  const ARGUMENT_ATTR_KEYS = [
1230
1241
  "gen_ai.tool.call.arguments",
@@ -1254,6 +1265,40 @@ function spanArguments(span) {
1254
1265
  reason: "the span carries no argument evidence"
1255
1266
  };
1256
1267
  }
1268
+ /** The OTel GenAI attribute listing the tool definitions offered to the
1269
+ * model: an array (or its JSON string) of `{ name }`, `{ function: { name } }`
1270
+ * or plain names. */
1271
+ const TOOL_DEFINITIONS_ATTR = "gen_ai.tool.definitions";
1272
+ /** The tool names a span records as offered to the model. */
1273
+ function spanOfferedTools(span) {
1274
+ const raw = span.attributes?.[TOOL_DEFINITIONS_ATTR];
1275
+ if (raw === void 0 || raw === null) return { recorded: false };
1276
+ const value = parseJsonString(raw);
1277
+ if (!Array.isArray(value)) return {
1278
+ recorded: true,
1279
+ unreadable: `${TOOL_DEFINITIONS_ATTR} is not a list`
1280
+ };
1281
+ const names = [];
1282
+ for (const entry of value) {
1283
+ const name = typeof entry === "string" ? entry : definitionName(entry);
1284
+ if (name === void 0) return {
1285
+ recorded: true,
1286
+ unreadable: `${TOOL_DEFINITIONS_ATTR} has an entry without a tool name`
1287
+ };
1288
+ names.push(name);
1289
+ }
1290
+ return {
1291
+ recorded: true,
1292
+ names
1293
+ };
1294
+ }
1295
+ function definitionName(entry) {
1296
+ if (entry === null || typeof entry !== "object") return void 0;
1297
+ const record = entry;
1298
+ if (typeof record.name === "string" && record.name.length > 0) return record.name;
1299
+ const fn = record.function;
1300
+ return fn !== null && typeof fn === "object" && typeof fn.name === "string" && fn.name.length > 0 ? fn.name : void 0;
1301
+ }
1257
1302
  function parseJsonString(value) {
1258
1303
  if (typeof value !== "string") return value;
1259
1304
  try {
@@ -1402,6 +1447,14 @@ function assertJsonPointer(pointer, where) {
1402
1447
  if (typeof pointer !== "string" || pointer !== "" && !pointer.startsWith("/")) throw new ValidationError(`${where}: pointer must be an RFC 6901 JSON Pointer ('' or '/a/b')`);
1403
1448
  if (/~(?![01])/.test(pointer)) throw new ValidationError(`${where}: pointer has an invalid escape; use ~0 for ~ and ~1 for /`);
1404
1449
  }
1450
+ /** A non-empty list of distinct, non-empty tool names. */
1451
+ function assertNames(names, where) {
1452
+ if (!Array.isArray(names) || names.length === 0) throw new ValidationError(`${where} must be a non-empty array of tool names`);
1453
+ for (const name of names) if (typeof name !== "string" || name.length === 0) throw new ValidationError(`${where} must hold non-empty tool names`);
1454
+ const dup = names.find((n, i) => names.indexOf(n) !== i);
1455
+ if (dup !== void 0) throw new ValidationError(`${where} lists "${dup}" twice`);
1456
+ return names;
1457
+ }
1405
1458
  function assertArgumentCheck(check, where) {
1406
1459
  const c = check;
1407
1460
  if (c === null || typeof c !== "object") throw new ValidationError(`${where}: check must be an object`);
@@ -1442,7 +1495,7 @@ function assertRule(rule, where) {
1442
1495
  assertCount(rule.max, where);
1443
1496
  return;
1444
1497
  case "run":
1445
- if (rule.requireCompleted === void 0 && rule.allowedStatuses === void 0 && rule.maxDurationMs === void 0) throw new ValidationError(`${where}: run rule checks nothing`);
1498
+ if (!rule.requireCompleted && rule.allowedStatuses === void 0 && rule.maxDurationMs === void 0) throw new ValidationError(`${where}: run rule checks nothing`);
1446
1499
  if (rule.requireCompleted !== void 0 && typeof rule.requireCompleted !== "boolean") throw new ValidationError(`${where}: requireCompleted must be a boolean`);
1447
1500
  if (rule.allowedStatuses !== void 0) {
1448
1501
  if (!Array.isArray(rule.allowedStatuses) || rule.allowedStatuses.length === 0) throw new ValidationError(`${where}: allowedStatuses must be a non-empty array`);
@@ -1456,6 +1509,25 @@ function assertRule(rule, where) {
1456
1509
  assertArgumentCheck(rule.check, where);
1457
1510
  if (rule.occurrence !== void 0 && !ARGUMENT_OCCURRENCES.includes(rule.occurrence)) throw new ValidationError(`${where}: occurrence must be one of ${ARGUMENT_OCCURRENCES.join(", ")}`);
1458
1511
  return;
1512
+ case "toolsOffered":
1513
+ if (rule.declared !== void 0) assertNames(rule.declared, `${where}: declared`);
1514
+ return;
1515
+ case "retrySafe": {
1516
+ const reads = rule.reads === void 0 ? [] : assertNames(rule.reads, `${where}: reads`);
1517
+ if (rule.writes === void 0 && rule.reads === void 0) throw new ValidationError(`${where}: retrySafe needs reads or writes`);
1518
+ if (rule.writes !== void 0) {
1519
+ if (!Array.isArray(rule.writes) || rule.writes.length === 0) throw new ValidationError(`${where}: writes must be a non-empty array`);
1520
+ const writes = assertNames(rule.writes.map((w) => w?.tool), `${where}: writes[].tool`);
1521
+ for (const [i, w] of rule.writes.entries()) {
1522
+ if (w.idempotencyKey === void 0) continue;
1523
+ assertJsonPointer(w.idempotencyKey, `${where}: writes[${i}].idempotencyKey`);
1524
+ if (w.idempotencyKey === "") throw new ValidationError(`${where}: writes[${i}].idempotencyKey must name a member, not the whole arguments`);
1525
+ }
1526
+ const both = writes.find((t) => reads.includes(t));
1527
+ if (both !== void 0) throw new ValidationError(`${where}: "${both}" is both a read and a write`);
1528
+ }
1529
+ return;
1530
+ }
1459
1531
  default: throw new ValidationError(`${where}: unknown rule kind "${String(rule.kind)}"`);
1460
1532
  }
1461
1533
  }
@@ -1525,12 +1597,19 @@ function describeRule(rule) {
1525
1597
  case "tokensAtMost": return `spans matching ${describePredicate(rule.p)} use at most ${rule.max} tokens in total`;
1526
1598
  case "run": {
1527
1599
  const parts = [];
1528
- if (rule.requireCompleted) parts.push("the run reached a terminal status");
1600
+ if (rule.requireCompleted) parts.push("the run completed and recorded its end");
1529
1601
  if (rule.allowedStatuses) parts.push(`its status is one of ${rule.allowedStatuses.join(", ")}`);
1530
1602
  if (rule.maxDurationMs !== void 0) parts.push(`it took at most ${rule.maxDurationMs} ms`);
1531
1603
  return parts.join("; ");
1532
1604
  }
1533
1605
  case "argument": return `${rule.occurrence ?? "all"} call(s) matching ${describePredicate(rule.p)} have argument ${rule.pointer || "(whole value)"} that ${describeCheck(rule.check)}`;
1606
+ case "toolsOffered": return `the trace records the tools offered to the model, every tool call is one of them${rule.declared ? `, and every offered tool is one of ${rule.declared.join(", ")}` : ""}`;
1607
+ case "retrySafe": {
1608
+ const parts = ["no tool is called twice with the same arguments"];
1609
+ if (rule.reads) parts.push(`except the reads ${rule.reads.join(", ")}`);
1610
+ for (const w of rule.writes ?? []) parts.push(w.idempotencyKey === void 0 ? `write ${w.tool} may never repeat` : `write ${w.tool} may repeat only with one idempotency key at ${w.idempotencyKey}`);
1611
+ return parts.join("; ");
1612
+ }
1534
1613
  }
1535
1614
  }
1536
1615
  /** Plain-language statement of everything a contract checks, one rule per line. */
@@ -1660,6 +1739,28 @@ var TraceContractBuilder = class TraceContractBuilder {
1660
1739
  assertRule(rule, this.where("argument"));
1661
1740
  return this.add(rule);
1662
1741
  }
1742
+ /** The trace records the tools offered to the model, every call is one of
1743
+ * them, and, with `declared`, every offered tool is declared. */
1744
+ toolsOffered(declared, label = "toolsOffered") {
1745
+ const rule = {
1746
+ kind: "toolsOffered",
1747
+ label,
1748
+ ...declared === void 0 ? {} : { declared: [...declared] }
1749
+ };
1750
+ assertRule(rule, this.where("toolsOffered"));
1751
+ return this.add(rule);
1752
+ }
1753
+ /** No call repeats a side effect that is not proven safe to repeat. */
1754
+ retrySafe(options, label = "retrySafe") {
1755
+ const rule = {
1756
+ kind: "retrySafe",
1757
+ label,
1758
+ ...options.reads === void 0 ? {} : { reads: [...options.reads] },
1759
+ ...options.writes === void 0 ? {} : { writes: options.writes.map((w) => ({ ...w })) }
1760
+ };
1761
+ assertRule(rule, this.where("retrySafe"));
1762
+ return this.add(rule);
1763
+ }
1663
1764
  /** Evaluate only the subtree rooted at the one span matching `root`. */
1664
1765
  scope(root) {
1665
1766
  this.scopeRoot = normalizePredicate(root, this.where("scope"));
@@ -1868,9 +1969,9 @@ function checkRun(rule, context) {
1868
1969
  }];
1869
1970
  const run = context.run;
1870
1971
  const out = [];
1871
- if (rule.requireCompleted && (run.status === void 0 || run.status === "running" || finite(run.endedAt) === void 0)) out.push({
1972
+ if (rule.requireCompleted && (run.status !== "completed" || finite(run.endedAt) === void 0)) out.push({
1872
1973
  rule: rule.label,
1873
- detail: `run did not reach a terminal status (status ${run.status ?? "unknown"})`
1974
+ detail: run.status === "completed" ? "run status is completed but no end time was recorded" : `run status is ${run.status ?? "unknown"}, not completed`
1874
1975
  });
1875
1976
  if (rule.allowedStatuses && !rule.allowedStatuses.includes(run.status ?? "")) out.push({
1876
1977
  rule: rule.label,
@@ -1890,6 +1991,188 @@ function checkRun(rule, context) {
1890
1991
  }
1891
1992
  return out;
1892
1993
  }
1994
+ const listed = (names, max = 6) => names.length <= max ? names.join(", ") : `${names.slice(0, max).join(", ")} and ${names.length - max} more`;
1995
+ function toolSpans(spans) {
1996
+ const out = [];
1997
+ spans.forEach((span, i) => {
1998
+ if (contractSpanKind(span) === "TOOL") out.push({
1999
+ span,
2000
+ ref: spanRef(span, i)
2001
+ });
2002
+ });
2003
+ return out;
2004
+ }
2005
+ /** Declared, offered and called tools agree. */
2006
+ function checkToolsOffered(rule, spans) {
2007
+ const out = [];
2008
+ const offered = /* @__PURE__ */ new Set();
2009
+ let recorded = 0;
2010
+ const declared = rule.declared ? new Set(rule.declared) : void 0;
2011
+ spans.forEach((span, i) => {
2012
+ const tools = spanOfferedTools(span);
2013
+ if (!tools.recorded) return;
2014
+ recorded += 1;
2015
+ const ref = spanRef(span, i);
2016
+ if ("unreadable" in tools) {
2017
+ out.push({
2018
+ rule: rule.label,
2019
+ spanId: span.spanId,
2020
+ detail: `span ${ref}: ${tools.unreadable}`
2021
+ });
2022
+ return;
2023
+ }
2024
+ for (const name of tools.names) offered.add(name);
2025
+ const undeclared = declared ? [...new Set(tools.names)].filter((n) => !declared.has(n)) : [];
2026
+ if (undeclared.length > 0) out.push({
2027
+ rule: rule.label,
2028
+ spanId: span.spanId,
2029
+ detail: `span ${ref} offered the model ${undeclared.length} undeclared tool(s): ${listed(undeclared)}`
2030
+ });
2031
+ });
2032
+ if (recorded === 0) return [{
2033
+ rule: rule.label,
2034
+ detail: `no span records the tools offered to the model (${TOOL_DEFINITIONS_ATTR}), so what the harness enforced is unknown`
2035
+ }];
2036
+ for (const { span, ref } of toolSpans(spans)) {
2037
+ const name = contractSpanToolName(span);
2038
+ if (name === void 0) out.push({
2039
+ rule: rule.label,
2040
+ spanId: span.spanId,
2041
+ detail: `span ${ref} has no tool name, so it cannot be matched to an offered tool`
2042
+ });
2043
+ else if (!offered.has(name)) out.push({
2044
+ rule: rule.label,
2045
+ spanId: span.spanId,
2046
+ detail: `span ${ref} called ${name}, which was never offered: the harness did not enforce its tool set`
2047
+ });
2048
+ }
2049
+ return out;
2050
+ }
2051
+ /** The call's idempotency key, or why it has none. */
2052
+ function idempotencyKey(span, pointer) {
2053
+ const evidence = spanArguments(span);
2054
+ if (!evidence.known) return { missing: evidence.reason };
2055
+ const resolved = resolveJsonPointer(evidence.value, pointer);
2056
+ const value = resolved.value;
2057
+ if (resolved.found && typeof value === "string" && value.length > 0) return { key: value };
2058
+ if (resolved.found && typeof value === "number" && Number.isFinite(value)) return { key: String(value) };
2059
+ return { missing: `no idempotency key at ${pointer}` };
2060
+ }
2061
+ /** No call repeats a side effect that is not proven safe to repeat. */
2062
+ function checkRetrySafe(rule, spans) {
2063
+ const reads = new Set(rule.reads ?? []);
2064
+ const writes = new Map((rule.writes ?? []).map((w) => [w.tool, w]));
2065
+ const out = [];
2066
+ const groups = /* @__PURE__ */ new Map();
2067
+ const unseen = /* @__PURE__ */ new Map();
2068
+ for (const call of toolSpans(spans)) {
2069
+ const name = contractSpanToolName(call.span);
2070
+ if (name === void 0) {
2071
+ out.push({
2072
+ rule: rule.label,
2073
+ spanId: call.span.spanId,
2074
+ detail: `span ${call.ref} has no tool name, so whether it repeats a call is unknown`
2075
+ });
2076
+ continue;
2077
+ }
2078
+ if (reads.has(name)) continue;
2079
+ const args = spanArguments(call.span);
2080
+ if (!args.known) {
2081
+ const list = unseen.get(name) ?? [];
2082
+ list.push(call);
2083
+ unseen.set(name, list);
2084
+ continue;
2085
+ }
2086
+ const pointer = writes.get(name)?.idempotencyKey;
2087
+ const key = `${name}\u0000${canonicalString(pointer === void 0 ? args.value : withoutMember(args.value, pointer))}`;
2088
+ const list = groups.get(key) ?? [];
2089
+ list.push(call);
2090
+ groups.set(key, list);
2091
+ }
2092
+ for (const [name, calls] of unseen) {
2093
+ const total = calls.length + [...groups].reduce((n, [k, list]) => k.startsWith(`${name}\u0000`) ? n + list.length : n, 0);
2094
+ if (total > 1) out.push({
2095
+ rule: rule.label,
2096
+ spanId: calls[0].span.spanId,
2097
+ detail: `${name} was called ${total} times and span ${calls[0].ref} has no captured arguments, so whether a call repeats another is unknown`
2098
+ });
2099
+ }
2100
+ for (const [key, calls] of groups) {
2101
+ if (calls.length < 2) continue;
2102
+ const name = key.slice(0, key.indexOf("\0"));
2103
+ const refs = listed(calls.map((c) => c.ref));
2104
+ const write = writes.get(name);
2105
+ if (!write) {
2106
+ out.push({
2107
+ rule: rule.label,
2108
+ spanId: calls[1].span.spanId,
2109
+ detail: `${name} was called ${calls.length} times with the same arguments (spans ${refs}) and its side effect is not declared, so the repeat is not proven safe`
2110
+ });
2111
+ continue;
2112
+ }
2113
+ const pointer = write.idempotencyKey;
2114
+ if (pointer === void 0) {
2115
+ out.push({
2116
+ rule: rule.label,
2117
+ spanId: calls[1].span.spanId,
2118
+ detail: `write ${name} was called ${calls.length} times with the same arguments (spans ${refs}) and declares no idempotency key, so it may have applied more than once`
2119
+ });
2120
+ continue;
2121
+ }
2122
+ const keyed = calls.map((c) => ({
2123
+ c,
2124
+ k: idempotencyKey(c.span, pointer)
2125
+ }));
2126
+ const missing = keyed.find((x) => "missing" in x.k);
2127
+ const keys = new Set(keyed.flatMap((x) => "key" in x.k ? [x.k.key] : []));
2128
+ if (missing) out.push({
2129
+ rule: rule.label,
2130
+ spanId: missing.c.span.spanId,
2131
+ detail: `write ${name} was called ${calls.length} times with the same arguments (spans ${refs}); span ${missing.c.ref} has ${missing.k.missing}, so it may have applied more than once`
2132
+ });
2133
+ else if (keys.size > 1) out.push({
2134
+ rule: rule.label,
2135
+ spanId: calls[1].span.spanId,
2136
+ detail: `write ${name} was called ${calls.length} times with the same arguments (spans ${refs}) under ${keys.size} different idempotency keys, so the target cannot tell they are one operation`
2137
+ });
2138
+ }
2139
+ const flagged = new Set(out.map((v) => v.spanId));
2140
+ for (const call of toolSpans(spans)) {
2141
+ const name = contractSpanToolName(call.span);
2142
+ if (name === void 0 || flagged.has(call.span.spanId)) continue;
2143
+ const write = writes.get(name);
2144
+ if (!write || !isSpanError(call.span)) continue;
2145
+ const pointer = write.idempotencyKey;
2146
+ if (!(pointer !== void 0 && "key" in idempotencyKey(call.span, pointer))) {
2147
+ out.push({
2148
+ rule: rule.label,
2149
+ spanId: call.span.spanId,
2150
+ detail: `write ${name} (span ${call.ref}) errored and ${pointer === void 0 ? "declares no idempotency key" : `has ${idempotencyKey(call.span, pointer).missing}`}; its outcome is unknown, so a retry cannot be proven safe`
2151
+ });
2152
+ flagged.add(call.span.spanId);
2153
+ }
2154
+ }
2155
+ return out;
2156
+ }
2157
+ /** A copy of `value` without the member `pointer` names (RFC 6901). */
2158
+ function withoutMember(value, pointer) {
2159
+ const tokens = pointer.slice(1).split("/").map((t) => t.replace(/~1/g, "/").replace(/~0/g, "~"));
2160
+ const strip = (node, depth) => {
2161
+ const token = tokens[depth];
2162
+ const last = depth === tokens.length - 1;
2163
+ if (Array.isArray(node)) {
2164
+ const index = /^(?:0|[1-9]\d*)$/.test(token) ? Number(token) : -1;
2165
+ if (index < 0 || index >= node.length) return node;
2166
+ return last ? node.filter((_, i) => i !== index) : node.map((v, i) => i === index ? strip(v, depth + 1) : v);
2167
+ }
2168
+ if (node === null || typeof node !== "object" || !Object.hasOwn(node, token)) return node;
2169
+ const copy = { ...node };
2170
+ if (last) delete copy[token];
2171
+ else copy[token] = strip(copy[token], depth + 1);
2172
+ return copy;
2173
+ };
2174
+ return strip(value, 0);
2175
+ }
1893
2176
  function checkRule(rule, ctx) {
1894
2177
  const { spans } = ctx;
1895
2178
  switch (rule.kind) {
@@ -1947,6 +2230,8 @@ function checkRule(rule, ctx) {
1947
2230
  }
1948
2231
  case "run": return checkRun(rule, ctx.run);
1949
2232
  case "argument": return checkArgument(rule, spans);
2233
+ case "toolsOffered": return checkToolsOffered(rule, spans);
2234
+ case "retrySafe": return checkRetrySafe(rule, spans);
1950
2235
  }
1951
2236
  }
1952
2237
  function executeRule(rule, ctx) {
@@ -1969,12 +2254,16 @@ function executeRule(rule, ctx) {
1969
2254
  /**
1970
2255
  * A run derived from one span: the `run.status` attribute a run-anchor span
1971
2256
  * carries (store-to-otlp writes it), else failed on an error status, else
1972
- * completed once the span ended.
2257
+ * unknown. An ended span with an OK status and no declared `run.status` is
2258
+ * NOT inferred as completed — a span exporter can end its root span cleanly
2259
+ * without ever learning whether the underlying run actually finished (for
2260
+ * example, a trace reader that saw no terminal record). `requireCompleted`
2261
+ * and `allowedStatuses` must fail closed on that gap rather than pass it.
1973
2262
  */
1974
2263
  function runOfSpan(span) {
1975
2264
  const declared = span.attributes?.["run.status"];
1976
2265
  return {
1977
- status: typeof declared === "string" && RUN_STATUSES.includes(declared) ? declared : isSpanError(span) ? "failed" : finite(span.endedAt) !== void 0 ? "completed" : "running",
2266
+ status: typeof declared === "string" && RUN_STATUSES.includes(declared) ? declared : isSpanError(span) ? "failed" : void 0,
1978
2267
  ...finite(span.startedAt) === void 0 ? {} : { startedAt: span.startedAt },
1979
2268
  ...finite(span.endedAt) === void 0 ? {} : { endedAt: span.endedAt }
1980
2269
  };
@@ -2008,8 +2297,8 @@ function statusOf(outcomes) {
2008
2297
  }
2009
2298
  /**
2010
2299
  * Evaluate one contract over a span sequence. Pure and synchronous — works on
2011
- * `Span[]` from a TraceStore, `ExportableSpan[]` from the otel-bridge
2012
- * flattening, or any array satisfying `ContractSpan`. Throws
2300
+ * `Span[]` from a TraceStore, `contractSpansFromOtlp` output, or any array
2301
+ * satisfying `ContractSpan`. Throws
2013
2302
  * `ValidationError` for a malformed contract; a rule that cannot be evaluated
2014
2303
  * is reported as `error` in the verdict instead.
2015
2304
  */
@@ -2108,7 +2397,9 @@ function rulePredicates(rule) {
2108
2397
  switch (rule.kind) {
2109
2398
  case "precedes": return [rule.a, rule.b];
2110
2399
  case "neverUnless": return [rule.p, rule.prior];
2111
- case "run": return [];
2400
+ case "run":
2401
+ case "toolsOffered":
2402
+ case "retrySafe": return [];
2112
2403
  default: return [rule.p];
2113
2404
  }
2114
2405
  }
@@ -2145,7 +2436,12 @@ function checkTraceContracts(spans, contracts, options = {}) {
2145
2436
  * "allowed": ["lookup_order", "issue_refund", "send_email"],
2146
2437
  * "maxCalls": 20,
2147
2438
  * "requiredOrder": ["lookup_order", "issue_refund"],
2148
- * "arguments": [{ "tool": "issue_refund", "pointer": "/amount", "type": "number" }]
2439
+ * "arguments": [{ "tool": "issue_refund", "pointer": "/amount", "type": "number" }],
2440
+ * "enforced": true
2441
+ * },
2442
+ * "retries": {
2443
+ * "reads": ["lookup_order"],
2444
+ * "writes": [{ "tool": "issue_refund", "idempotencyKey": "/idempotency_key" }]
2149
2445
  * },
2150
2446
  * "llm": { "maxCalls": 10, "maxTotalTokens": 50000, "allowedModels": ["claude-sonnet-4-5"] },
2151
2447
  * "alternatives": { "anyOf": [{ "id": "cache-hit", "tools": { "required": ["read_cache"] } }] }
@@ -2162,6 +2458,7 @@ const PATH_KEYS = [
2162
2458
  "run",
2163
2459
  "tools",
2164
2460
  "llm",
2461
+ "retries",
2165
2462
  "rules"
2166
2463
  ];
2167
2464
  const SPEC_KEYS = [
@@ -2185,8 +2482,11 @@ const TOOLS_KEYS = [
2185
2482
  "maxCallsPerTool",
2186
2483
  "requiredOrder",
2187
2484
  "orderMode",
2188
- "arguments"
2485
+ "arguments",
2486
+ "enforced"
2189
2487
  ];
2488
+ const RETRIES_KEYS = ["reads", "writes"];
2489
+ const WRITE_KEYS = ["tool", "idempotencyKey"];
2190
2490
  const LLM_KEYS = [
2191
2491
  "maxCalls",
2192
2492
  "maxTotalTokens",
@@ -2274,6 +2574,17 @@ const RULE_KEYS = {
2274
2574
  "pointer",
2275
2575
  "check",
2276
2576
  "occurrence"
2577
+ ],
2578
+ toolsOffered: [
2579
+ "kind",
2580
+ "label",
2581
+ "declared"
2582
+ ],
2583
+ retrySafe: [
2584
+ "kind",
2585
+ "label",
2586
+ "reads",
2587
+ "writes"
2277
2588
  ]
2278
2589
  };
2279
2590
  /**
@@ -2368,7 +2679,18 @@ function compilePath(path, prefix) {
2368
2679
  ...arg.occurrence === void 0 ? {} : { occurrence: arg.occurrence }
2369
2680
  });
2370
2681
  });
2682
+ if (tools.enforced) rules.push({
2683
+ kind: "toolsOffered",
2684
+ label: at("tools.enforced"),
2685
+ ...tools.allowed === void 0 ? {} : { declared: [...tools.allowed] }
2686
+ });
2371
2687
  }
2688
+ if (path.retries) rules.push({
2689
+ kind: "retrySafe",
2690
+ label: at("retries"),
2691
+ ...path.retries.reads === void 0 ? {} : { reads: [...path.retries.reads] },
2692
+ ...path.retries.writes === void 0 ? {} : { writes: path.retries.writes.map((w) => ({ ...w })) }
2693
+ });
2372
2694
  const llm = path.llm;
2373
2695
  if (llm) {
2374
2696
  if (llm.maxCalls !== void 0) rules.push({
@@ -2442,6 +2764,7 @@ function parsePath(path, prefix) {
2442
2764
  if (path.run !== void 0) {
2443
2765
  const run = object(path.run, at("run"));
2444
2766
  strictKeys(run, RUN_KEYS, at("run"));
2767
+ if (run.requireCompleted !== void 0 && run.requireCompleted !== true) throw new ValidationError(`${at("run.requireCompleted")}: must be true`);
2445
2768
  if (Array.isArray(run.allowedStatuses)) run.allowedStatuses.forEach((status, i) => {
2446
2769
  oneOfValues(status, RUN_STATUSES, at(`run.allowedStatuses[${i}]`));
2447
2770
  });
@@ -2463,11 +2786,13 @@ function parsePath(path, prefix) {
2463
2786
  if (tools.maxCalls !== void 0) count(tools.maxCalls, at("tools.maxCalls"));
2464
2787
  if (tools.maxCallsPerTool !== void 0) {
2465
2788
  const perTool = object(tools.maxCallsPerTool, at("tools.maxCallsPerTool"));
2789
+ if (Object.keys(perTool).length === 0) throw new ValidationError(`${at("tools.maxCallsPerTool")}: must not be empty`);
2466
2790
  for (const [tool, max] of Object.entries(perTool)) {
2467
2791
  nonEmptyString(tool, at("tools.maxCallsPerTool key"));
2468
2792
  count(max, at(`tools.maxCallsPerTool.${tool}`));
2469
2793
  }
2470
2794
  }
2795
+ if (tools.enforced !== void 0 && tools.enforced !== true) throw new ValidationError(`${at("tools.enforced")}: must be true`);
2471
2796
  if (tools.orderMode !== void 0) oneOfValues(tools.orderMode, ORDER_MODES, at("tools.orderMode"));
2472
2797
  if (tools.orderMode !== void 0 && tools.requiredOrder === void 0) throw new ValidationError(`${at("tools.orderMode")}: set only with requiredOrder`);
2473
2798
  if (tools.arguments !== void 0) nonEmptyArray(tools.arguments, at("tools.arguments")).forEach((arg, i) => {
@@ -2490,9 +2815,25 @@ function parsePath(path, prefix) {
2490
2815
  }, where);
2491
2816
  });
2492
2817
  }
2818
+ if (path.retries !== void 0) {
2819
+ const retries = object(path.retries, at("retries"));
2820
+ strictKeys(retries, RETRIES_KEYS, at("retries"));
2821
+ if (retries.reads !== void 0) toolNames(retries.reads, at("retries.reads"));
2822
+ if (retries.writes !== void 0) nonEmptyArray(retries.writes, at("retries.writes")).forEach((w, i) => {
2823
+ const where = at(`retries.writes[${i}]`);
2824
+ strictKeys(object(w, where), WRITE_KEYS, where);
2825
+ nonEmptyString(w.tool, `${where}.tool`);
2826
+ });
2827
+ assertRule({
2828
+ kind: "retrySafe",
2829
+ label: "retries",
2830
+ ...retries
2831
+ }, at("retries"));
2832
+ }
2493
2833
  if (path.llm !== void 0) {
2494
2834
  const llm = object(path.llm, at("llm"));
2495
2835
  strictKeys(llm, LLM_KEYS, at("llm"));
2836
+ if (llm.maxCalls === void 0 && llm.maxTotalTokens === void 0 && llm.allowedModels === void 0) throw new ValidationError(`${at("llm")}: llm rule checks nothing`);
2496
2837
  if (llm.maxCalls !== void 0) count(llm.maxCalls, at("llm.maxCalls"));
2497
2838
  if (llm.maxTotalTokens !== void 0) count(llm.maxTotalTokens, at("llm.maxTotalTokens"));
2498
2839
  if (llm.allowedModels !== void 0) toolNames(llm.allowedModels, at("llm.allowedModels"));
@@ -2645,12 +2986,15 @@ function lintPath(path, prefix, out) {
2645
2986
  if (forbidden.has(arg.tool)) error("tools.argument-on-forbidden", `tools.arguments[${i}]`, `"${arg.tool}" is forbidden, so its arguments can never be checked`);
2646
2987
  if (!needed.includes(arg.tool)) warn("tools.argument-without-required", `tools.arguments[${i}]`, `"${arg.tool}" is not required; a run that never calls it fails this check for missing evidence`);
2647
2988
  }
2989
+ if (t.enforced && !allowed) warn("tools.enforced-without-allowed", "tools.enforced", "without allowed, only calls are checked against the offered tools; add allowed to also check what the harness offered");
2990
+ for (const [i, w] of (path.retries?.writes ?? []).entries()) if (forbidden.has(w.tool)) warn("retries.write-forbidden", `retries.writes[${i}]`, `"${w.tool}" is forbidden, so its retry rule never applies`);
2648
2991
  const order = t.requiredOrder ?? [];
2649
2992
  const repeated = order.find((tool, i) => order.indexOf(tool) !== i);
2650
2993
  if (repeated !== void 0) error("tools.order-repeats", "tools.requiredOrder", `"${repeated}" appears twice, so the order contradicts itself`);
2651
2994
  if (order.length > 1 && t.orderMode === void 0) warn("tools.order-default-mode", "tools.orderMode", "start-order passes when calls overlap; set finish-before-start if each step must finish first");
2652
2995
  const run = path.run;
2653
- if (run?.requireCompleted && run.allowedStatuses?.includes("running")) error("run.running-and-completed", "run", "requireCompleted contradicts allowedStatuses containing \"running\"");
2996
+ if (run?.requireCompleted && run.allowedStatuses && !run.allowedStatuses.includes("completed")) error("run.completed-not-allowed", "run", "requireCompleted needs status completed, which allowedStatuses excludes");
2997
+ else if (run?.requireCompleted && run.allowedStatuses?.some((s) => s !== "completed")) warn("run.statuses-beside-completed", "run.allowedStatuses", "requireCompleted accepts only completed, so the other allowed statuses never apply");
2654
2998
  if (path.llm?.maxCalls === 0 && (path.llm.maxTotalTokens !== void 0 || path.llm.allowedModels)) warn("llm.no-calls", "llm", "maxCalls is 0, so the token and model limits never apply");
2655
2999
  }
2656
3000
  //#endregion