@tangle-network/agent-eval 0.136.0 → 0.138.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 (231) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/README.md +37 -2
  3. package/dist/{agent-profile-cell-OhuTee9n.js → agent-profile-cell-CbfBm2g6.js} +2 -2
  4. package/dist/{agent-profile-cell-OhuTee9n.js.map → agent-profile-cell-CbfBm2g6.js.map} +1 -1
  5. package/dist/{agent-profile-cell-CCm3l2v2.d.ts → agent-profile-cell-Cw0PVwDr.d.ts} +2 -2
  6. package/dist/{agent-profile-cell-CCm3l2v2.d.ts.map → agent-profile-cell-Cw0PVwDr.d.ts.map} +1 -1
  7. package/dist/analyst/index.d.ts +574 -18
  8. package/dist/analyst/index.d.ts.map +1 -1
  9. package/dist/analyst/index.js +25 -5
  10. package/dist/analyst/index.js.map +1 -1
  11. package/dist/{analyze-runs-jjCmF8pU.js → analyze-runs-BScZvqMV.js} +6 -6
  12. package/dist/{analyze-runs-jjCmF8pU.js.map → analyze-runs-BScZvqMV.js.map} +1 -1
  13. package/dist/{analyze-runs-Cda5Xkj1.d.ts → analyze-runs-CPYxfPWT.d.ts} +6 -6
  14. package/dist/{analyze-runs-Cda5Xkj1.d.ts.map → analyze-runs-CPYxfPWT.d.ts.map} +1 -1
  15. package/dist/{baseline-BUeFcgrn.js → baseline-C-GocmIW.js} +2 -2
  16. package/dist/{baseline-BUeFcgrn.js.map → baseline-C-GocmIW.js.map} +1 -1
  17. package/dist/benchmark-D8dkki-J.js +554 -0
  18. package/dist/benchmark-D8dkki-J.js.map +1 -0
  19. package/dist/benchmark-DlQgU_XI.d.ts +236 -0
  20. package/dist/benchmark-DlQgU_XI.d.ts.map +1 -0
  21. package/dist/benchmark-command-CMqVqReF.js +4332 -0
  22. package/dist/benchmark-command-CMqVqReF.js.map +1 -0
  23. package/dist/benchmarks/index.d.ts +1 -1
  24. package/dist/benchmarks/index.js +1 -1
  25. package/dist/{benchmarks-Dfgm9ts5.js → benchmarks-BJ_xK5rQ.js} +4 -3
  26. package/dist/{benchmarks-Dfgm9ts5.js.map → benchmarks-BJ_xK5rQ.js.map} +1 -1
  27. package/dist/builder-eval/index.js +2 -2
  28. package/dist/campaign/index.d.ts +6 -6
  29. package/dist/campaign/index.js +4 -4
  30. package/dist/{campaign-Dz8uQnhC.js → campaign-BIBS-NHV.js} +219 -78
  31. package/dist/campaign-BIBS-NHV.js.map +1 -0
  32. package/dist/cli.js +9 -2
  33. package/dist/cli.js.map +1 -1
  34. package/dist/client-BwPKohkJ.d.ts +202 -0
  35. package/dist/client-BwPKohkJ.d.ts.map +1 -0
  36. package/dist/completion-verifier-B4-IMYcS.d.ts +240 -0
  37. package/dist/completion-verifier-B4-IMYcS.d.ts.map +1 -0
  38. package/dist/contract/index.d.ts +10 -9
  39. package/dist/contract/index.d.ts.map +1 -1
  40. package/dist/contract/index.js +13 -13
  41. package/dist/control.d.ts +2 -2
  42. package/dist/control.js +1 -1
  43. package/dist/{cost-ledger-fGS_u_O1.d.ts → cost-ledger-B1D3COAc.d.ts} +6 -5
  44. package/dist/{cost-ledger-fGS_u_O1.d.ts.map → cost-ledger-B1D3COAc.d.ts.map} +1 -1
  45. package/dist/{cost-ledger-DHAjwNj7.js → cost-ledger-CHDLA0Ss.js} +91 -46
  46. package/dist/cost-ledger-CHDLA0Ss.js.map +1 -0
  47. package/dist/{dataset-BvtnC8Dc.d.ts → dataset-v_Y5902-.d.ts} +2 -2
  48. package/dist/{dataset-BvtnC8Dc.d.ts.map → dataset-v_Y5902-.d.ts.map} +1 -1
  49. package/dist/{default-registry-Brxr728w.d.ts → default-registry-PUhIVRWz.d.ts} +77 -138
  50. package/dist/default-registry-PUhIVRWz.d.ts.map +1 -0
  51. package/dist/{default-registry-CHmdy2An.js → default-registry-lp5R0lve.js} +1617 -291
  52. package/dist/default-registry-lp5R0lve.js.map +1 -0
  53. package/dist/{errors-8YnH8WlF.js → errors-D-LKuDhb.js} +8 -2
  54. package/dist/errors-D-LKuDhb.js.map +1 -0
  55. package/dist/{errors-CEk209JS.d.ts → errors-DkfjIDvD.d.ts} +9 -3
  56. package/dist/errors-DkfjIDvD.d.ts.map +1 -0
  57. package/dist/{eval-campaign-Cc8WZJ6b.js → eval-campaign-9MozgKL7.js} +6 -6
  58. package/dist/{eval-campaign-Cc8WZJ6b.js.map → eval-campaign-9MozgKL7.js.map} +1 -1
  59. package/dist/exact-types-Dpw2LeHA.d.ts +234 -0
  60. package/dist/exact-types-Dpw2LeHA.d.ts.map +1 -0
  61. package/dist/{extract-usage-DIQpN-ww.js → extract-usage-CS391dOE.js} +3 -3
  62. package/dist/{extract-usage-DIQpN-ww.js.map → extract-usage-CS391dOE.js.map} +1 -1
  63. package/dist/{feedback-trajectory-CVaeREXV.d.ts → feedback-trajectory-CoNep7rl.d.ts} +91 -3
  64. package/dist/feedback-trajectory-CoNep7rl.d.ts.map +1 -0
  65. package/dist/fuzz.d.ts +1 -1
  66. package/dist/fuzz.js +2 -2
  67. package/dist/hosted/index.d.ts +3 -2
  68. package/dist/hosted/index.d.ts.map +1 -1
  69. package/dist/{index-AbhwHp0V.d.ts → index-B2-IxCMB.d.ts} +2 -2
  70. package/dist/{index-AbhwHp0V.d.ts.map → index-B2-IxCMB.d.ts.map} +1 -1
  71. package/dist/index-BipJlj-C.d.ts +316 -0
  72. package/dist/index-BipJlj-C.d.ts.map +1 -0
  73. package/dist/{index-CQsJcqch.d.ts → index-CjVYlVBK.d.ts} +5 -5
  74. package/dist/{index-CQsJcqch.d.ts.map → index-CjVYlVBK.d.ts.map} +1 -1
  75. package/dist/{index-B4Fjfo5U.d.ts → index-D0cxAdaV.d.ts} +89 -317
  76. package/dist/index-D0cxAdaV.d.ts.map +1 -0
  77. package/dist/{index-DuhJaaiH.d.ts → index-DEb46kc6.d.ts} +2 -2
  78. package/dist/index-DEb46kc6.d.ts.map +1 -0
  79. package/dist/{index-C2fkZhv_.d.ts → index-sMN_hI4E.d.ts} +3 -3
  80. package/dist/{index-C2fkZhv_.d.ts.map → index-sMN_hI4E.d.ts.map} +1 -1
  81. package/dist/index.d.ts +30 -70
  82. package/dist/index.d.ts.map +1 -1
  83. package/dist/index.js +175 -35
  84. package/dist/index.js.map +1 -1
  85. package/dist/{client-DcvgkaZi.d.ts → insight-report-CXd8VBDR.d.ts} +5 -203
  86. package/dist/insight-report-CXd8VBDR.d.ts.map +1 -0
  87. package/dist/{integrity-rmVhXWA7.d.ts → integrity-B-MLFz0I.d.ts} +3 -3
  88. package/dist/{integrity-rmVhXWA7.d.ts.map → integrity-B-MLFz0I.d.ts.map} +1 -1
  89. package/dist/integrity-CCXTftiL.js +1360 -0
  90. package/dist/integrity-CCXTftiL.js.map +1 -0
  91. package/dist/{integrity-BzRbCHzi.js → integrity-fdt8XPAv.js} +2 -2
  92. package/dist/{integrity-BzRbCHzi.js.map → integrity-fdt8XPAv.js.map} +1 -1
  93. package/dist/ledger-core/index.d.ts +1 -1
  94. package/dist/ledger-core/index.js +1 -1
  95. package/dist/{ledger-core-DAKFKRzi.js → ledger-core-C0Yx1I14.js} +303 -110
  96. package/dist/ledger-core-C0Yx1I14.js.map +1 -0
  97. package/dist/{llm-client-DHx8pzyJ.js → llm-client-Cj3c7PEm.js} +6 -6
  98. package/dist/llm-client-Cj3c7PEm.js.map +1 -0
  99. package/dist/meta-eval/index.d.ts +2 -2
  100. package/dist/meta-eval/index.js +3 -3
  101. package/dist/{mint-DyRUc9k6.js → mint-Ctwk079K.js} +4 -4
  102. package/dist/{mint-DyRUc9k6.js.map → mint-Ctwk079K.js.map} +1 -1
  103. package/dist/multishot/index.d.ts +2 -2
  104. package/dist/openapi.json +1 -1
  105. package/dist/{paired-arms-BbFKrAU-.js → paired-arms-iZ08VFMN.js} +3 -3
  106. package/dist/{paired-arms-BbFKrAU-.js.map → paired-arms-iZ08VFMN.js.map} +1 -1
  107. package/dist/pipelines/index.js +2 -2
  108. package/dist/profile-cell.d.ts +1 -1
  109. package/dist/profile-cell.js +1 -1
  110. package/dist/{proposal-findings-DCawte-y.js → proposal-findings-2GIUo1et.js} +2 -68
  111. package/dist/proposal-findings-2GIUo1et.js.map +1 -0
  112. package/dist/{propose-review-control-SQ-n9-We.js → propose-review-control-DLXz4FCX.js} +2 -2
  113. package/dist/{propose-review-control-SQ-n9-We.js.map → propose-review-control-DLXz4FCX.js.map} +1 -1
  114. package/dist/registry-C4yJTza7.d.ts +178 -0
  115. package/dist/registry-C4yJTza7.d.ts.map +1 -0
  116. package/dist/{release-report-DooPguBc.js → release-report-B5XPBvAU.js} +4 -4
  117. package/dist/{release-report-DooPguBc.js.map → release-report-B5XPBvAU.js.map} +1 -1
  118. package/dist/{release-report-DpBxGGI1.d.ts → release-report-CoyvyLBs.d.ts} +4 -4
  119. package/dist/{release-report-DpBxGGI1.d.ts.map → release-report-CoyvyLBs.d.ts.map} +1 -1
  120. package/dist/{replay-C6wRg47C.js → replay-Cb-4Vf0k.js} +249 -8
  121. package/dist/replay-Cb-4Vf0k.js.map +1 -0
  122. package/dist/{replay-BRfMIs81.d.ts → replay-DbIYwso6.d.ts} +227 -52
  123. package/dist/replay-DbIYwso6.d.ts.map +1 -0
  124. package/dist/reporting.d.ts +4 -4
  125. package/dist/reporting.js +4 -4
  126. package/dist/{researcher-Doo95b50.d.ts → researcher-BCeOEjtR.d.ts} +6 -7
  127. package/dist/researcher-BCeOEjtR.d.ts.map +1 -0
  128. package/dist/{reward-hacking-a-kYs0-i.js → reward-hacking-GyN0kMd8.js} +3 -3
  129. package/dist/{reward-hacking-a-kYs0-i.js.map → reward-hacking-GyN0kMd8.js.map} +1 -1
  130. package/dist/{reward-hacking-D-QqXvg-.d.ts → reward-hacking-sE2l_NV6.d.ts} +2 -2
  131. package/dist/{reward-hacking-D-QqXvg-.d.ts.map → reward-hacking-sE2l_NV6.d.ts.map} +1 -1
  132. package/dist/rl.d.ts +6 -6
  133. package/dist/rl.js +9 -9
  134. package/dist/rollout/index.d.ts +1 -1
  135. package/dist/rollout/index.js +3 -3
  136. package/dist/{rollout-DLSUIWLu.js → rollout-DQFl0UXA.js} +2 -2
  137. package/dist/{rollout-DLSUIWLu.js.map → rollout-DQFl0UXA.js.map} +1 -1
  138. package/dist/{rubric-predictive-validity-BJf-8ejY.js → rubric-predictive-validity-BRR632r1.js} +2 -2
  139. package/dist/{rubric-predictive-validity-BJf-8ejY.js.map → rubric-predictive-validity-BRR632r1.js.map} +1 -1
  140. package/dist/{rubric-predictive-validity-C1dCLcvb.d.ts → rubric-predictive-validity-w2klGv1u.d.ts} +2 -2
  141. package/dist/{rubric-predictive-validity-C1dCLcvb.d.ts.map → rubric-predictive-validity-w2klGv1u.d.ts.map} +1 -1
  142. package/dist/{run-evidence-DokQtX0-.d.ts → run-evidence-CbE0A8Xg.d.ts} +3 -3
  143. package/dist/{run-evidence-DokQtX0-.d.ts.map → run-evidence-CbE0A8Xg.d.ts.map} +1 -1
  144. package/dist/{run-record-DcObtIGh.d.ts → run-record-DwHMk1Ai.d.ts} +4 -4
  145. package/dist/{run-record-DcObtIGh.d.ts.map → run-record-DwHMk1Ai.d.ts.map} +1 -1
  146. package/dist/{run-record-BIwU2wdV.js → run-record-vRgqWmJw.js} +3 -3
  147. package/dist/{run-record-BIwU2wdV.js.map → run-record-vRgqWmJw.js.map} +1 -1
  148. package/dist/{semantic-concept-judge-Btozx3Vc.js → semantic-concept-judge-DYXDPZW0.js} +12 -6
  149. package/dist/semantic-concept-judge-DYXDPZW0.js.map +1 -0
  150. package/dist/{server-Bz3WQJs6.js → server-DLEvyW2z.js} +3 -3
  151. package/dist/{server-Bz3WQJs6.js.map → server-DLEvyW2z.js.map} +1 -1
  152. package/dist/single-run-lock-D_bS5xhj.js +318 -0
  153. package/dist/single-run-lock-D_bS5xhj.js.map +1 -0
  154. package/dist/{skill-usage-BDQVPIG1.d.ts → skill-usage-Bv3G4VkA.d.ts} +36 -48
  155. package/dist/skill-usage-Bv3G4VkA.d.ts.map +1 -0
  156. package/dist/{skillopt-optimization-method-0UmPD6aP.js → skillopt-optimization-method-CjKMZy0d.js} +10 -185
  157. package/dist/skillopt-optimization-method-CjKMZy0d.js.map +1 -0
  158. package/dist/{skillopt-optimization-method-CwSYkv35.d.ts → skillopt-optimization-method-CzfnA8O-.d.ts} +11 -12
  159. package/dist/skillopt-optimization-method-CzfnA8O-.d.ts.map +1 -0
  160. package/dist/{statistics-CnGCLLqc.js → statistics-ByxzSiOM.js} +2 -2
  161. package/dist/{statistics-CnGCLLqc.js.map → statistics-ByxzSiOM.js.map} +1 -1
  162. package/dist/{statistics-CKOqre5S.d.ts → statistics-mf70aXKp.d.ts} +2 -2
  163. package/dist/{statistics-CKOqre5S.d.ts.map → statistics-mf70aXKp.d.ts.map} +1 -1
  164. package/dist/store-otlp-BenKynPE.js +1688 -0
  165. package/dist/store-otlp-BenKynPE.js.map +1 -0
  166. package/dist/{summary-report-BEk8OFLs.js → summary-report-9A5y7EsK.js} +4 -4
  167. package/dist/{summary-report-BEk8OFLs.js.map → summary-report-9A5y7EsK.js.map} +1 -1
  168. package/dist/{summary-report-CPMINBqs.d.ts → summary-report-BKinV4yD.d.ts} +3 -3
  169. package/dist/{summary-report-CPMINBqs.d.ts.map → summary-report-BKinV4yD.d.ts.map} +1 -1
  170. package/dist/supervisor-run/index.d.ts +3 -2
  171. package/dist/supervisor-run/index.js +3 -2
  172. package/dist/{supervisor-run-Dr5HnTup.js → supervisor-run-B2EWUmQY.js} +28 -454
  173. package/dist/supervisor-run-B2EWUmQY.js.map +1 -0
  174. package/dist/{test-graded-scenario-BsqWLmPt.js → test-graded-scenario-JHcKQNpq.js} +2 -2
  175. package/dist/{test-graded-scenario-BsqWLmPt.js.map → test-graded-scenario-JHcKQNpq.js.map} +1 -1
  176. package/dist/tools-DZGdROtG.js +255 -0
  177. package/dist/tools-DZGdROtG.js.map +1 -0
  178. package/dist/traces.d.ts +6 -7
  179. package/dist/traces.js +6 -6
  180. package/dist/types-5q2T25iW.d.ts +804 -0
  181. package/dist/types-5q2T25iW.d.ts.map +1 -0
  182. package/dist/{types-DVjczBM9.d.ts → types-BtJhn8v6.d.ts} +260 -6
  183. package/dist/types-BtJhn8v6.d.ts.map +1 -0
  184. package/dist/{index-CyC1BTmn.d.ts → types-Dea6tiVI.d.ts} +16 -238
  185. package/dist/types-Dea6tiVI.d.ts.map +1 -0
  186. package/dist/{types-DiWLru6Z.d.ts → types-zFYez3PK.d.ts} +5 -5
  187. package/dist/{types-DiWLru6Z.d.ts.map → types-zFYez3PK.d.ts.map} +1 -1
  188. package/dist/wire/index.d.ts +3 -3
  189. package/dist/wire/index.js +1 -1
  190. package/docs/feedback-trajectories.md +100 -1
  191. package/docs/trace-analysis.md +494 -58
  192. package/package.json +9 -3
  193. package/dist/analyst-BkTS3C58.d.ts +0 -89
  194. package/dist/analyst-BkTS3C58.d.ts.map +0 -1
  195. package/dist/analyst-j5je5J7c.js +0 -152
  196. package/dist/analyst-j5je5J7c.js.map +0 -1
  197. package/dist/campaign-Dz8uQnhC.js.map +0 -1
  198. package/dist/client-DcvgkaZi.d.ts.map +0 -1
  199. package/dist/concurrency-MUjT7VjM.js +0 -109
  200. package/dist/concurrency-MUjT7VjM.js.map +0 -1
  201. package/dist/cost-ledger-DHAjwNj7.js.map +0 -1
  202. package/dist/default-registry-Brxr728w.d.ts.map +0 -1
  203. package/dist/default-registry-CHmdy2An.js.map +0 -1
  204. package/dist/errors-8YnH8WlF.js.map +0 -1
  205. package/dist/errors-CEk209JS.d.ts.map +0 -1
  206. package/dist/feedback-trajectory-CVaeREXV.d.ts.map +0 -1
  207. package/dist/index-B4Fjfo5U.d.ts.map +0 -1
  208. package/dist/index-CyC1BTmn.d.ts.map +0 -1
  209. package/dist/index-DuhJaaiH.d.ts.map +0 -1
  210. package/dist/ledger-core-DAKFKRzi.js.map +0 -1
  211. package/dist/llm-client-BiK4HW0u.d.ts +0 -290
  212. package/dist/llm-client-BiK4HW0u.d.ts.map +0 -1
  213. package/dist/llm-client-DHx8pzyJ.js.map +0 -1
  214. package/dist/proposal-findings-DCawte-y.js.map +0 -1
  215. package/dist/raw-provider-sink-BU29Sh8h.d.ts +0 -134
  216. package/dist/raw-provider-sink-BU29Sh8h.d.ts.map +0 -1
  217. package/dist/replay-BRfMIs81.d.ts.map +0 -1
  218. package/dist/replay-C6wRg47C.js.map +0 -1
  219. package/dist/researcher-Doo95b50.d.ts.map +0 -1
  220. package/dist/semantic-concept-judge-Btozx3Vc.js.map +0 -1
  221. package/dist/skill-usage-BDQVPIG1.d.ts.map +0 -1
  222. package/dist/skillopt-optimization-method-0UmPD6aP.js.map +0 -1
  223. package/dist/skillopt-optimization-method-CwSYkv35.d.ts.map +0 -1
  224. package/dist/store-CxJry_cs.d.ts +0 -229
  225. package/dist/store-CxJry_cs.d.ts.map +0 -1
  226. package/dist/supervisor-run-Dr5HnTup.js.map +0 -1
  227. package/dist/tools-D8yTtNSN.js +0 -1190
  228. package/dist/tools-D8yTtNSN.js.map +0 -1
  229. package/dist/types-Cc3qbqzj.d.ts +0 -387
  230. package/dist/types-Cc3qbqzj.d.ts.map +0 -1
  231. package/dist/types-DVjczBM9.d.ts.map +0 -1
@@ -1,9 +1,13 @@
1
- import { i as CostLedger } from "./cost-ledger-DHAjwNj7.js";
2
- import { f as maximumChargeForLlmRequest, l as costReceiptFromLlm, n as LlmClient, s as callLlm, u as costReceiptFromLlmError } from "./llm-client-DHx8pzyJ.js";
1
+ import { i as CostLedger } from "./cost-ledger-CHDLA0Ss.js";
2
+ import { f as maximumChargeForLlmRequest, l as costReceiptFromLlm, n as LlmClient, s as callLlm, u as costReceiptFromLlmError } from "./llm-client-Cj3c7PEm.js";
3
3
  import { LLM_CONTEXT_TOKENS, LLM_INPUT_TOKEN_ATTR_KEYS, LLM_OUTPUT_TOKEN_ATTR_KEYS, TOOL_NAME_ATTR_KEYS } from "./trace-attributes.js";
4
4
  import { t as executionTrackByLane } from "./execution-tracks-CpgFPpS5.js";
5
- import { S as spanEpochMillis, r as runTraceAnalysisLoop, t as buildTraceAnalystTools } from "./tools-D8yTtNSN.js";
6
- import { c as makeFinding, o as combineAbortSignals } from "./proposal-findings-DCawte-y.js";
5
+ import { D as spanEpochMillis } from "./store-otlp-BenKynPE.js";
6
+ import { f as validateUsageSettlementTimeout, l as assertValidAnalystUsageReceipt, m as makeFinding, u as settleUsageReceiptFromCostLedger } from "./single-run-lock-D_bS5xhj.js";
7
+ import { _ as canonicalString, v as hashCanonical } from "./ledger-core-C0Yx1I14.js";
8
+ import { a as runTraceAnalysisLoop, r as buildTraceAnalystTools } from "./tools-DZGdROtG.js";
9
+ import { t as analyzeSupervisorRunIntegrity } from "./integrity-CCXTftiL.js";
10
+ import { o as combineAbortSignals } from "./proposal-findings-2GIUo1et.js";
7
11
  import { ai } from "@ax-llm/ax";
8
12
  import { z } from "zod";
9
13
  import { randomUUID } from "node:crypto";
@@ -134,11 +138,8 @@ function resolveModel(req, defaultModel) {
134
138
  /**
135
139
  * Deterministic behavioral metrics over OTLP spans — pure arithmetic, no LLM.
136
140
  *
137
- * These are the model-independent multiplier: the four trace-quality signals a
138
- * tolerant analyzer (e.g. HALO) re-derives per run inside the model — token
139
- * growth, output decay, tool monoculture, missing self-verification — computed
140
- * here once, in TypeScript, with zero model judgment. A finding that falls out
141
- * of arithmetic is trivially model-agnostic and cannot hallucinate the trend.
141
+ * It computes token growth, output decay, tool monoculture, and missing
142
+ * self-verification once in TypeScript with no model judgment.
142
143
  *
143
144
  * General, not trace-specific: the detectors key off token trajectories and
144
145
  * tool usage present in any agentic OTLP trace, not any one benchmark.
@@ -472,18 +473,9 @@ function everyAdjacent(values, predicate) {
472
473
  //#endregion
473
474
  //#region src/analyst/behavioral-analyst.ts
474
475
  /**
475
- * `behavioralAnalyst` a DETERMINISTIC analyst (cost.kind = 'deterministic',
476
- * never calls the LLM). It produces the efficiency/behavioral findings a
477
- * tolerant agentic analyzer (HALO) re-derives per run inside the model —
478
- * context bloat, output decay, tool monoculture, missing self-verification —
479
- * directly from arithmetic over spans (`computeTraceMetrics`).
480
- *
481
- * Why it matters: these findings are model-agnostic BY CONSTRUCTION (no model
482
- * in the loop), so they cannot return 0 on a weak model the way the Ax-RLM
483
- * does — and they are strictly more reliable than HALO, which spends tokens
484
- * re-deriving the same numbers and can hallucinate the trend. The agentic
485
- * RLM kinds remain for SEMANTIC findings that genuinely need a model; this
486
- * analyst owns the behavioral class.
476
+ * Deterministic behavioral analysis over arithmetic in trace spans.
477
+ * This pass is cheap and repeatable; semantic analysis remains the job of
478
+ * model-backed analysts. Relative quality requires a labeled comparison.
487
479
  */
488
480
  const RECOMMENDED_ACTION = {
489
481
  "monotonic-input-growth": "Inspect context assembly; if prior history is repeatedly included, summarize completed work before the next model call.",
@@ -491,19 +483,44 @@ const RECOMMENDED_ACTION = {
491
483
  "single-tool-dependency": "Test whether an inspect or verification tool improves outcomes after the repeated call fails or returns no progress.",
492
484
  "no-self-verification": "After state-changing actions, require an observable check before the agent proceeds."
493
485
  };
494
- const ANALYST_ID = "efficiency-behavioral";
486
+ const ANALYST_ID$1 = "efficiency-behavioral";
487
+ const DEFAULT_MAX_TRACES = 1e3;
488
+ const DEFAULT_MAX_EVIDENCE_REFS = 20;
489
+ const TRACE_PAGE_SIZE = 200;
495
490
  const AGGREGATE_CLAIM = {
496
491
  "monotonic-input-growth": (observed, analyzed) => `${observed}/${analyzed} analyzed traces showed input tokens grow from zero to nonzero or to at least 3x their initial value across at least 3 serial model calls without a decrease.`,
497
492
  "output-length-decay": (observed, analyzed) => `${observed}/${analyzed} analyzed traces showed output tokens decrease while input tokens increased monotonically across at least 3 serial model calls.`,
498
493
  "single-tool-dependency": (observed, analyzed) => `${observed}/${analyzed} analyzed traces used only one named tool across at least 3 tool calls.`,
499
494
  "no-self-verification": (observed, analyzed) => `${observed}/${analyzed} analyzed traces had at least 3 tool calls without a verification-named tool call.`
500
495
  };
496
+ async function listTraceIds(store, maxTraces, signal) {
497
+ const traceIds = /* @__PURE__ */ new Set();
498
+ let offset = 0;
499
+ let expectedTotal;
500
+ while (true) {
501
+ signal?.throwIfAborted();
502
+ const page = await store.queryTraces({
503
+ limit: TRACE_PAGE_SIZE,
504
+ offset
505
+ });
506
+ if (expectedTotal === void 0) expectedTotal = page.total;
507
+ if (page.total !== expectedTotal) throw new Error(`behavioralAnalyst: trace count changed during pagination (${expectedTotal} to ${page.total})`);
508
+ if (page.total > maxTraces) throw new RangeError(`behavioralAnalyst: ${page.total} traces exceed maxTraces=${maxTraces}; filter the store or raise the explicit limit`);
509
+ for (const trace of page.traces) traceIds.add(trace.trace_id);
510
+ if (traceIds.size > maxTraces) throw new RangeError(`behavioralAnalyst: more than maxTraces=${maxTraces} unique traces were returned`);
511
+ if (!page.has_more) break;
512
+ if (page.traces.length === 0) throw new Error("behavioralAnalyst: trace store returned an empty page with has_more=true");
513
+ offset += page.traces.length;
514
+ }
515
+ if (traceIds.size !== expectedTotal) throw new Error(`behavioralAnalyst: pagination returned ${traceIds.size}/${expectedTotal ?? 0} unique traces`);
516
+ return [...traceIds].sort();
517
+ }
501
518
  /**
502
519
  * Map computed signals → structured AnalystFindings. Pure: no LLM, no clock
503
520
  * dependence beyond `produced_at` (overridable for deterministic tests).
504
521
  */
505
522
  function deriveEfficiencyFindings(metrics, opts = {}) {
506
- const analystId = opts.analystId ?? ANALYST_ID;
523
+ const analystId = opts.analystId ?? ANALYST_ID$1;
507
524
  const traceId = metrics.traceId;
508
525
  return metrics.signals.map((sig) => makeFinding({
509
526
  analyst_id: analystId,
@@ -528,18 +545,25 @@ function deriveEfficiencyFindings(metrics, opts = {}) {
528
545
  }));
529
546
  }
530
547
  /** The deterministic behavioral/efficiency analyst (no LLM, any-model). */
531
- function behavioralAnalyst() {
548
+ function behavioralAnalyst(options = {}) {
549
+ const maxTraces = positiveInteger(options.maxTraces ?? DEFAULT_MAX_TRACES, "maxTraces");
550
+ const maxEvidenceRefsPerFinding = positiveInteger(options.maxEvidenceRefsPerFinding ?? DEFAULT_MAX_EVIDENCE_REFS, "maxEvidenceRefsPerFinding");
532
551
  return {
533
- id: ANALYST_ID,
552
+ id: ANALYST_ID$1,
534
553
  description: "Deterministic behavioral/efficiency findings over OTLP spans — token-growth, output-decay, tool-monoculture, missing self-verification. Zero LLM; model-agnostic by construction.",
535
554
  inputKind: "trace-store",
536
555
  cost: { kind: "deterministic" },
537
556
  version: "2.0.0",
538
- async analyze(store) {
539
- const overview = await store.getOverview();
540
- const analyzedTraceIds = [...new Set(overview.sample_trace_ids)].sort();
557
+ executionConfig: {
558
+ kind: "behavioral-efficiency",
559
+ max_traces: maxTraces,
560
+ max_evidence_refs_per_finding: maxEvidenceRefsPerFinding
561
+ },
562
+ async analyze(store, context) {
563
+ const analyzedTraceIds = await listTraceIds(store, maxTraces, context.signal);
541
564
  const findingsById = /* @__PURE__ */ new Map();
542
565
  for (const traceId of analyzedTraceIds) {
566
+ context.signal?.throwIfAborted();
543
567
  const viewed = await store.viewTrace({ trace_id: traceId });
544
568
  if (viewed.trace_id !== traceId) throw new Error(`behavioralAnalyst: requested trace '${traceId}', received '${viewed.trace_id}'`);
545
569
  if (!viewed.spans) throw new Error(`behavioralAnalyst: trace '${traceId}' is oversized; complete spans are required`);
@@ -550,30 +574,39 @@ function behavioralAnalyst() {
550
574
  if (!current) {
551
575
  findingsById.set(finding.finding_id, {
552
576
  finding,
553
- traceIds: [traceId],
577
+ observedTraceCount: 1,
578
+ evidenceTraceIds: [traceId],
554
579
  evidence: [...finding.evidence_refs]
555
580
  });
556
581
  continue;
557
582
  }
558
- current.traceIds.push(traceId);
559
- current.evidence.push(...finding.evidence_refs);
583
+ current.observedTraceCount += 1;
584
+ if (current.evidence.length < maxEvidenceRefsPerFinding) {
585
+ current.evidenceTraceIds.push(traceId);
586
+ current.evidence.push(...finding.evidence_refs);
587
+ }
560
588
  }
561
589
  }
562
- return [...findingsById.values()].map(({ finding, traceIds, evidence }) => ({
590
+ return [...findingsById.values()].map(({ finding, observedTraceCount, evidenceTraceIds, evidence }) => ({
563
591
  ...finding,
564
- claim: AGGREGATE_CLAIM[finding.subject](traceIds.length, analyzedTraceIds.length),
565
- rationale: `${traceIds.length}/${analyzedTraceIds.length} analyzed traces exhibited this pattern.`,
592
+ claim: AGGREGATE_CLAIM[finding.subject](observedTraceCount, analyzedTraceIds.length),
593
+ rationale: `${observedTraceCount}/${analyzedTraceIds.length} analyzed traces exhibited this pattern.`,
566
594
  evidence_refs: evidence,
567
595
  metadata: {
568
596
  deterministic: true,
569
- trace_ids: traceIds,
570
- observed_trace_count: traceIds.length,
597
+ evidence_trace_ids: evidenceTraceIds,
598
+ omitted_evidence_trace_count: observedTraceCount - evidenceTraceIds.length,
599
+ observed_trace_count: observedTraceCount,
571
600
  analyzed_trace_count: analyzedTraceIds.length
572
601
  }
573
602
  }));
574
603
  }
575
604
  };
576
605
  }
606
+ function positiveInteger(value, name) {
607
+ if (!Number.isSafeInteger(value) || value < 1) throw new RangeError(`behavioralAnalyst: ${name} must be a positive safe integer`);
608
+ return value;
609
+ }
577
610
  //#endregion
578
611
  //#region src/analyst/ax-cost-service.ts
579
612
  /**
@@ -667,6 +700,7 @@ function boundOutputTokens(request, limit) {
667
700
  const maxTokens = requested === void 0 ? limit : Math.min(requested, limit);
668
701
  return {
669
702
  ...request,
703
+ ...request.functionCall === void 0 && !request.functions?.length ? { functionCall: "none" } : {},
670
704
  modelConfig: {
671
705
  ...request.modelConfig,
672
706
  maxTokens,
@@ -775,6 +809,221 @@ function assertPositiveInteger(value, field) {
775
809
  if (!Number.isSafeInteger(value) || value <= 0) throw new RangeError(`meterAxChatService: ${field} must be a positive integer`);
776
810
  }
777
811
  //#endregion
812
+ //#region src/ledger-core/deep-freeze.ts
813
+ /** Freeze a detached canonical-JSON graph. Canonicalization has already ruled out cycles.
814
+ *
815
+ * Lives outside canonical.ts so the analyst-benchmark implementation digest,
816
+ * which covers canonical.ts, stays bound to the published benchmark evidence. */
817
+ function deepFreezeCanonicalJson(value) {
818
+ if (value && typeof value === "object" && !Object.isFrozen(value)) {
819
+ Object.freeze(value);
820
+ for (const nested of Object.values(value)) deepFreezeCanonicalJson(nested);
821
+ }
822
+ return value;
823
+ }
824
+ //#endregion
825
+ //#region src/analyst/exact-types.ts
826
+ /** Canonical identity for any live component admitted to an exact run. */
827
+ function snapshotExactExecutionComponentIdentity(value, context) {
828
+ let detached;
829
+ try {
830
+ detached = JSON.parse(canonicalString(value));
831
+ } catch (cause) {
832
+ throw new TypeError(`${context} must have a canonical JSON representation`, { cause });
833
+ }
834
+ const parsed = componentIdentitySchema.safeParse(detached);
835
+ if (!parsed.success) throw new TypeError(`${context} requires non-empty id/version and object config`);
836
+ return deepFreezeCanonicalJson({
837
+ id: parsed.data.id,
838
+ version: parsed.data.version,
839
+ config_digest: hashCanonical(parsed.data.config)
840
+ });
841
+ }
842
+ const nonEmptyString = z.string().min(1);
843
+ const digest = z.string().regex(/^sha256:[a-f0-9]{64}$/);
844
+ const finiteNonnegative$1 = z.number().finite().nonnegative();
845
+ const nonnegativeSafeInteger$1 = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
846
+ const positiveTimeout = z.number().int().positive().max(2147483647);
847
+ const componentSnapshotSchema = z.strictObject({
848
+ id: nonEmptyString,
849
+ version: nonEmptyString,
850
+ config_digest: digest
851
+ });
852
+ const componentIdentitySchema = z.strictObject({
853
+ id: nonEmptyString,
854
+ version: nonEmptyString,
855
+ config: z.record(z.string(), z.unknown())
856
+ });
857
+ const deterministicCostSchema = z.strictObject({
858
+ kind: z.literal("deterministic"),
859
+ est_usd_per_run: finiteNonnegative$1.optional(),
860
+ models: z.array(nonEmptyString).optional()
861
+ });
862
+ const llmCostSchema = z.strictObject({
863
+ kind: z.literal("llm"),
864
+ est_usd_per_run: finiteNonnegative$1.optional(),
865
+ models: z.array(nonEmptyString).optional(),
866
+ settlement_timeout_ms: nonnegativeSafeInteger$1.optional()
867
+ });
868
+ const requirementsSchema = z.strictObject({
869
+ min_shots: nonnegativeSafeInteger$1.optional(),
870
+ capabilities: z.array(nonEmptyString).optional()
871
+ }).nullable();
872
+ const analystSnapshotSchema = z.strictObject({
873
+ id: nonEmptyString,
874
+ version: nonEmptyString,
875
+ input_kind: z.enum([
876
+ "trace-store",
877
+ "artifact-dir",
878
+ "run-record",
879
+ "judge-input",
880
+ "custom"
881
+ ]),
882
+ cost: z.discriminatedUnion("kind", [deterministicCostSchema, llmCostSchema]),
883
+ requirements: requirementsSchema,
884
+ execution_config_digest: digest
885
+ });
886
+ const allocationsSchema = z.record(nonEmptyString, z.union([finiteNonnegative$1, z.null()]));
887
+ const weightsSchema = z.record(nonEmptyString, finiteNonnegative$1);
888
+ const budgetSnapshotSchema = z.discriminatedUnion("kind", [
889
+ z.strictObject({ kind: z.literal("none") }),
890
+ z.strictObject({
891
+ kind: z.literal("equal"),
892
+ total_usd: finiteNonnegative$1,
893
+ allocations_usd: allocationsSchema
894
+ }),
895
+ z.strictObject({
896
+ kind: z.literal("weighted"),
897
+ total_usd: finiteNonnegative$1,
898
+ weights: weightsSchema,
899
+ allocations_usd: allocationsSchema
900
+ })
901
+ ]);
902
+ const priorFindingsSchema = z.discriminatedUnion("kind", [
903
+ z.strictObject({ kind: z.literal("none") }),
904
+ z.strictObject({
905
+ kind: z.literal("ordered"),
906
+ count: nonnegativeSafeInteger$1,
907
+ digest
908
+ }),
909
+ z.strictObject({
910
+ kind: z.literal("by_analyst"),
911
+ keys: z.array(nonEmptyString),
912
+ count: nonnegativeSafeInteger$1,
913
+ digest
914
+ })
915
+ ]);
916
+ const exactRunPolicySchema = z.strictObject({
917
+ budget: budgetSnapshotSchema,
918
+ total_timeout_ms: positiveTimeout.nullable(),
919
+ signal_provided: z.boolean(),
920
+ cost_ledger: componentSnapshotSchema.nullable(),
921
+ cost_phase: nonEmptyString.nullable(),
922
+ tags: z.record(z.string(), z.string()).nullable(),
923
+ prior_findings: priorFindingsSchema,
924
+ chain_findings: z.boolean(),
925
+ missing_input_mode: z.enum(["skip", "abort"]),
926
+ registry_hooks: componentSnapshotSchema.nullable(),
927
+ registry_chat: componentSnapshotSchema.nullable()
928
+ });
929
+ const exactExecutionPlanSchema = z.strictObject({
930
+ schema_version: z.literal("1.0.0"),
931
+ analysts: z.array(analystSnapshotSchema).min(1),
932
+ policy: exactRunPolicySchema,
933
+ digest
934
+ }).superRefine((plan, context) => {
935
+ const issue = (path, message) => context.addIssue({
936
+ code: "custom",
937
+ path,
938
+ message
939
+ });
940
+ const analystIds = plan.analysts.map((analyst) => analyst.id);
941
+ if (new Set(analystIds).size !== analystIds.length) issue(["analysts"], "analyst ids must be unique");
942
+ if (plan.policy.cost_ledger === null && plan.policy.cost_phase !== null) issue(["policy", "cost_phase"], "cost phase requires a cost ledger");
943
+ if (plan.policy.prior_findings.kind === "by_analyst" && plan.policy.prior_findings.keys.some((key, index, keys) => index > 0 && key <= keys[index - 1])) issue([
944
+ "policy",
945
+ "prior_findings",
946
+ "keys"
947
+ ], "keys must be sorted and unique");
948
+ const budget = plan.policy.budget;
949
+ if (budget.kind === "none") return;
950
+ const allocationIds = Object.keys(budget.allocations_usd).sort();
951
+ const selectedIds = [...analystIds].sort();
952
+ if (allocationIds.length !== selectedIds.length || allocationIds.some((id, index) => id !== selectedIds[index])) {
953
+ issue([
954
+ "policy",
955
+ "budget",
956
+ "allocations_usd"
957
+ ], "allocations must name every analyst and no others");
958
+ return;
959
+ }
960
+ const runnableIds = analystIds.filter((id) => budget.allocations_usd[id] !== null);
961
+ const epsilon = Math.max(1, budget.total_usd) * Number.EPSILON * 8;
962
+ if (runnableIds.length === 0) return;
963
+ if (budget.kind === "weighted") {
964
+ const weightIds = Object.keys(budget.weights).sort();
965
+ if (weightIds.length !== selectedIds.length || weightIds.some((id, index) => id !== selectedIds[index])) {
966
+ issue([
967
+ "policy",
968
+ "budget",
969
+ "weights"
970
+ ], "weights must name every analyst and no others");
971
+ return;
972
+ }
973
+ const totalWeight = runnableIds.reduce((sum, id) => sum + (budget.weights[id] ?? 0), 0);
974
+ if (totalWeight === 0) {
975
+ issue([
976
+ "policy",
977
+ "budget",
978
+ "weights"
979
+ ], "runnable analysts must have positive total weight");
980
+ return;
981
+ }
982
+ for (const id of runnableIds) {
983
+ const expected = budget.total_usd * (budget.weights[id] ?? 0) / totalWeight;
984
+ if (Math.abs((budget.allocations_usd[id] ?? 0) - expected) > epsilon) issue([
985
+ "policy",
986
+ "budget",
987
+ "allocations_usd",
988
+ id
989
+ ], "allocation does not match the weighted policy");
990
+ }
991
+ return;
992
+ }
993
+ const expected = budget.total_usd / runnableIds.length;
994
+ for (const id of runnableIds) if (Math.abs((budget.allocations_usd[id] ?? 0) - expected) > epsilon) issue([
995
+ "policy",
996
+ "budget",
997
+ "allocations_usd",
998
+ id
999
+ ], "allocation does not match the equal policy");
1000
+ });
1001
+ /**
1002
+ * Canonicalize and validate the one exact-plan representation shared by execution and archival.
1003
+ * Unknown fields fail at every level; the returned graph is detached and deeply frozen.
1004
+ */
1005
+ function snapshotExactExecutionPlan(value, context = "exact analyst execution plan") {
1006
+ let detached;
1007
+ try {
1008
+ detached = JSON.parse(canonicalString(value));
1009
+ } catch (cause) {
1010
+ throw new TypeError(`${context} must have a canonical JSON representation`, { cause });
1011
+ }
1012
+ const parsed = exactExecutionPlanSchema.safeParse(detached);
1013
+ if (!parsed.success) {
1014
+ const issue = parsed.error.issues[0];
1015
+ const path = issue?.path.length ? ` ${issue.path.join(".")}` : "";
1016
+ throw new TypeError(`${context}${path}: ${issue?.message ?? "is invalid"}`);
1017
+ }
1018
+ const expectedDigest = hashCanonical({
1019
+ schema_version: parsed.data.schema_version,
1020
+ analysts: parsed.data.analysts,
1021
+ policy: parsed.data.policy
1022
+ });
1023
+ if (parsed.data.digest !== expectedDigest) throw new TypeError(`${context} digest does not match its content`);
1024
+ return deepFreezeCanonicalJson(parsed.data);
1025
+ }
1026
+ //#endregion
778
1027
  //#region src/analyst/finding-subject.ts
779
1028
  /**
780
1029
  * Typed `FindingSubject` — the canonical grammar every analyst kind emits.
@@ -1286,6 +1535,7 @@ function parseFindingWithSchema(schema, row, log) {
1286
1535
  }
1287
1536
  function evidenceKindFromUri(uri) {
1288
1537
  if (uri.startsWith("span://")) return "span";
1538
+ if (/^trace:\/\/[^/]+\/span\/[^/]+$/.test(uri)) return "span";
1289
1539
  if (uri.startsWith("event://")) return "event";
1290
1540
  if (uri.startsWith("finding://")) return "finding";
1291
1541
  if (uri.startsWith("metric://")) return "metric";
@@ -1399,49 +1649,6 @@ async function structureFindings(opts) {
1399
1649
  outcome: "extraction_failed"
1400
1650
  };
1401
1651
  }
1402
- /** Convert one ledger channel's complete call set into one analyst receipt. */
1403
- function usageReceiptFromCostLedger(ledger, filter = "analyst") {
1404
- const resolvedFilter = typeof filter === "string" ? { channel: filter } : filter;
1405
- const summary = ledger.summary(resolvedFilter);
1406
- const receipts = ledger.list(resolvedFilter);
1407
- const hasReasoningUsage = receipts.some((receipt) => receipt.reasoningTokens !== void 0);
1408
- const hasCacheWriteUsage = receipts.some((receipt) => receipt.cacheWriteTokens !== void 0);
1409
- const cost = summary.costProvenance;
1410
- return {
1411
- calls: summary.totalCalls + summary.pendingCalls,
1412
- tokens: summary.usageComplete ? {
1413
- input: summary.inputTokens,
1414
- output: summary.outputTokens,
1415
- ...hasReasoningUsage ? { reasoning: summary.reasoningTokens ?? 0 } : {},
1416
- ...summary.cachedTokens > 0 ? { cached: summary.cachedTokens } : {},
1417
- ...hasCacheWriteUsage ? { cacheWrite: summary.cacheWriteTokens ?? 0 } : {}
1418
- } : null,
1419
- cost,
1420
- ...cost.kind === "uncaptured" ? { knownCostUsd: summary.totalCostUsd } : {}
1421
- };
1422
- }
1423
- /** Wait a bounded time for late provider receipts, then take one immutable snapshot. */
1424
- async function settleUsageReceiptFromCostLedger(ledger, options = {}) {
1425
- const { timeoutMs: requestedTimeoutMs, ...requestedFilter } = options;
1426
- const filter = {
1427
- channel: requestedFilter.channel ?? "analyst",
1428
- ...requestedFilter.phase === void 0 ? {} : { phase: requestedFilter.phase },
1429
- ...requestedFilter.tags === void 0 ? {} : { tags: requestedFilter.tags }
1430
- };
1431
- const timeoutMs = validateUsageSettlementTimeout(requestedTimeoutMs);
1432
- const waitResult = ledger.summary(filter).pendingCalls === 0 ? true : ledger.waitForIdle ? await ledger.waitForIdle({ timeoutMs }) : false;
1433
- const pendingCalls = ledger.summary(filter).pendingCalls;
1434
- return {
1435
- settled: waitResult && pendingCalls === 0,
1436
- pendingCalls,
1437
- receipt: usageReceiptFromCostLedger(ledger, filter)
1438
- };
1439
- }
1440
- function validateUsageSettlementTimeout(timeoutMs) {
1441
- const resolved = timeoutMs ?? 5e3;
1442
- if (!Number.isSafeInteger(resolved) || resolved < 0 || resolved > 2147483647) throw new TypeError("settlementTimeoutMs must be a non-negative safe integer no greater than 2147483647");
1443
- return resolved;
1444
- }
1445
1652
  //#endregion
1446
1653
  //#region src/analyst/kind-factory.ts
1447
1654
  /**
@@ -1459,6 +1666,8 @@ function createTraceAnalystKind(spec, opts) {
1459
1666
  const minimumEvidenceCitations = spec.minimumEvidenceCitations ?? 1;
1460
1667
  if (!Number.isInteger(minimumEvidenceCitations) || minimumEvidenceCitations < 1) throw new TypeError("minimumEvidenceCitations must be a positive integer");
1461
1668
  const settlementTimeoutMs = validateUsageSettlementTimeout(opts.settlementTimeoutMs);
1669
+ const maxOutputTokens = spec.maxOutputTokens ?? 4096;
1670
+ const aiIdentity = opts.aiIdentity === void 0 ? null : snapshotExactExecutionComponentIdentity(opts.aiIdentity, "createTraceAnalystKind aiIdentity");
1462
1671
  return {
1463
1672
  id: spec.id,
1464
1673
  description: spec.description,
@@ -1468,8 +1677,29 @@ function createTraceAnalystKind(spec, opts) {
1468
1677
  settlement_timeout_ms: settlementTimeoutMs
1469
1678
  },
1470
1679
  version,
1680
+ executionConfig: {
1681
+ kind: "trace-analyst",
1682
+ model,
1683
+ ai_identity: aiIdentity,
1684
+ actor_description_digest: hashCanonical(spec.actorDescription.trim()),
1685
+ max_subqueries: spec.subqueries?.maxCalls ?? 0,
1686
+ max_parallel_subqueries: spec.subqueries?.maxParallel ?? 2,
1687
+ max_turns: spec.maxTurns ?? 12,
1688
+ max_runtime_chars: spec.maxRuntimeChars ?? 6e3,
1689
+ max_output_tokens: maxOutputTokens,
1690
+ minimum_evidence_citations: minimumEvidenceCitations,
1691
+ require_structured_findings: spec.requireStructuredFindings ?? false,
1692
+ prepare_context: spec.prepareContext === void 0 ? "disabled" : "version-bound",
1693
+ post_process: spec.postProcess === void 0 ? "disabled" : "version-bound",
1694
+ recovery: opts.recovery === void 0 ? null : {
1695
+ base_url: opts.recovery.baseUrl,
1696
+ model: opts.recovery.model ?? model,
1697
+ api_key_provided: opts.recovery.apiKey !== void 0,
1698
+ fetch_implementation: opts.recovery.fetchImpl === void 0 ? "global" : "version-bound"
1699
+ },
1700
+ settlement_timeout_ms: settlementTimeoutMs
1701
+ },
1471
1702
  async analyze(store, ctx) {
1472
- const maxOutputTokens = spec.maxOutputTokens ?? 4096;
1473
1703
  const costLedger = ctx.costLedger ?? new CostLedger(ctx.budgetUsd);
1474
1704
  const costTags = {
1475
1705
  ...ctx.tags ?? {},
@@ -1486,7 +1716,10 @@ function createTraceAnalystKind(spec, opts) {
1486
1716
  tags: costTags
1487
1717
  });
1488
1718
  try {
1489
- const tools = spec.buildTools(store);
1719
+ const preparedContext = await spec.prepareContext?.(store, ctx);
1720
+ if (preparedContext !== void 0 && typeof preparedContext !== "string") throw new TypeError(`Trace analyst '${spec.id}' prepareContext must return a string`);
1721
+ const tools = preparedContext === void 0 ? spec.buildTools(store) : [];
1722
+ const analysisMode = preparedContext === void 0 ? "tool-loop" : "prepared-context";
1490
1723
  const maxSubqueries = spec.subqueries?.maxCalls ?? 0;
1491
1724
  const maxParallel = spec.subqueries?.maxParallel ?? 2;
1492
1725
  const priorContext = renderPriorFindings(ctx.priorFindings);
@@ -1495,9 +1728,11 @@ function createTraceAnalystKind(spec, opts) {
1495
1728
  ctx.log?.(`analyst.kind ${spec.id} forward`, {
1496
1729
  max_subqueries: maxSubqueries,
1497
1730
  tool_count: tools.length,
1731
+ analysis_mode: analysisMode,
1732
+ prepared_context_chars: preparedContext?.length ?? 0,
1498
1733
  tags: ctx.tags
1499
1734
  });
1500
- const { report, findings: submittedFindings } = await runTraceAnalysisLoop({
1735
+ const completed = await runTraceAnalysisLoop({
1501
1736
  id: spec.id,
1502
1737
  description: spec.description,
1503
1738
  prompt: actorDescription,
@@ -1510,8 +1745,10 @@ function createTraceAnalystKind(spec, opts) {
1510
1745
  maxParallelSubqueries: maxParallel,
1511
1746
  maxTurns: spec.maxTurns ?? 12,
1512
1747
  maxRuntimeChars: spec.maxRuntimeChars ?? 6e3,
1748
+ ...preparedContext !== void 0 ? { context: preparedContext } : {},
1513
1749
  ...ctx.signal ? { signal: ctx.signal } : {}
1514
1750
  });
1751
+ const { report, findings: submittedFindings } = completed;
1515
1752
  const expectedSubjects = KIND_EXPECTED_SUBJECTS[spec.id];
1516
1753
  const out = [];
1517
1754
  const rawRows = submittedFindings;
@@ -1561,7 +1798,10 @@ function createTraceAnalystKind(spec, opts) {
1561
1798
  if (!parsed) continue;
1562
1799
  const postProcessed = processRow(parsed);
1563
1800
  if (!postProcessed) continue;
1564
- out.push(toAnalystFinding(spec, version, postProcessed));
1801
+ out.push(toAnalystFinding(spec, version, postProcessed, {
1802
+ analysis_mode: analysisMode,
1803
+ analysis_turn_count: completed.turnCount
1804
+ }));
1565
1805
  }
1566
1806
  ctx.log?.(`analyst.kind ${spec.id} done`, {
1567
1807
  emitted: rawRows.length,
@@ -1598,6 +1838,7 @@ function createTraceAnalystKind(spec, opts) {
1598
1838
  });
1599
1839
  }
1600
1840
  if (out.length === 0) {
1841
+ if (spec.requireStructuredFindings) throw new Error(`Trace analyst '${spec.id}' produced no valid structured findings after ${completed.turnCount} turns: ${truncateForContext(report, 600)}`);
1601
1842
  const fallback = processRow({
1602
1843
  claim: "Analyst produced a diagnosis but no structured findings — see report.",
1603
1844
  rationale: report.slice(0, 1500),
@@ -1608,7 +1849,11 @@ function createTraceAnalystKind(spec, opts) {
1608
1849
  excerpt: report.slice(0, 2e3)
1609
1850
  }]
1610
1851
  });
1611
- if (fallback) out.push(toAnalystFinding(spec, version, fallback, { outcome: "extraction_failed" }));
1852
+ if (fallback) out.push(toAnalystFinding(spec, version, fallback, {
1853
+ analysis_mode: analysisMode,
1854
+ analysis_turn_count: completed.turnCount,
1855
+ outcome: "extraction_failed"
1856
+ }));
1612
1857
  else throw new Error(`Trace analyst '${spec.id}' produced a substantive report, but no finding satisfied its acceptance rules`);
1613
1858
  }
1614
1859
  }
@@ -1719,6 +1964,66 @@ function truncateForContext(s, max) {
1719
1964
  return `${s.slice(0, max - 1).trimEnd()}…`;
1720
1965
  }
1721
1966
  //#endregion
1967
+ //#region src/analyst/kinds/control-integrity.ts
1968
+ const ANALYST_ID = "control-integrity";
1969
+ function shown(value) {
1970
+ if (value === void 0) return "<absent>";
1971
+ const encoded = JSON.stringify(value);
1972
+ return encoded === void 0 ? String(value) : encoded;
1973
+ }
1974
+ function evidenceRef(namespace, value) {
1975
+ return {
1976
+ kind: "metric",
1977
+ uri: `supervisor-run://${encodeURIComponent(namespace)}/${value.path}`,
1978
+ excerpt: shown(value.value)
1979
+ };
1980
+ }
1981
+ /** Translate typed supervisor-run integrity issues into the shared analyst envelope. */
1982
+ function emitControlIntegrityFindings(input, producedAt) {
1983
+ const report = analyzeSupervisorRunIntegrity(input, { capturedAt: producedAt });
1984
+ return report.issues.map((issue) => makeFinding({
1985
+ analyst_id: ANALYST_ID,
1986
+ produced_at: producedAt,
1987
+ area: issue.area,
1988
+ severity: issue.severity,
1989
+ subject: `${report.runRef}/${issue.subject}`,
1990
+ claim: issue.claim,
1991
+ rationale: issue.detail,
1992
+ evidence_refs: issue.evidence.map((value) => evidenceRef(report.runRef, value)),
1993
+ recommended_action: issue.recommendedAction,
1994
+ validation_plan: "Re-run this deterministic analyst on the retained SupervisorRunSources or SupervisorRunTree after correcting the producer.",
1995
+ confidence: 1,
1996
+ metadata: {
1997
+ integrity_code: issue.code,
1998
+ integrity_input: report.input,
1999
+ integrity_run_ref: report.runRef,
2000
+ integrity_subject: issue.subject,
2001
+ ...issue.metadata
2002
+ }
2003
+ }));
2004
+ }
2005
+ /** Deterministic Analyst adapter for `SupervisorRunSources | SupervisorRunTree`. */
2006
+ var ControlIntegrityAnalyst = class {
2007
+ id = ANALYST_ID;
2008
+ description = "Deterministic supervisor-run integrity checks with explicit unavailable evidence.";
2009
+ inputKind = "custom";
2010
+ cost = {
2011
+ kind: "deterministic",
2012
+ est_usd_per_run: 0
2013
+ };
2014
+ version = "2.0.0";
2015
+ executionConfig = {
2016
+ kind: "control-integrity",
2017
+ produced_at_source: "tags.producedAt-or-system-clock"
2018
+ };
2019
+ async analyze(input, ctx) {
2020
+ const findings = emitControlIntegrityFindings(input, ctx.tags?.producedAt ?? (/* @__PURE__ */ new Date()).toISOString());
2021
+ ctx.log?.(`control-integrity: ${findings.length} finding(s)`, { input: "nodes" in input ? "SupervisorRunTree" : "SupervisorRunSources" });
2022
+ return findings;
2023
+ }
2024
+ };
2025
+ const CONTROL_INTEGRITY_ANALYST = new ControlIntegrityAnalyst();
2026
+ //#endregion
1722
2027
  //#region src/analyst/tool-groups.ts
1723
2028
  const TOOL_NAMES_BY_GROUP = {
1724
2029
  all: /* @__PURE__ */ new Set(),
@@ -1746,6 +2051,13 @@ const TOOL_NAMES_BY_GROUP = {
1746
2051
  "queryTraces",
1747
2052
  "viewSpans",
1748
2053
  "searchSpan"
2054
+ ]),
2055
+ singleTrace: /* @__PURE__ */ new Set([
2056
+ "getDatasetOverview",
2057
+ "viewTrace",
2058
+ "viewSpans",
2059
+ "searchTrace",
2060
+ "searchSpan"
1749
2061
  ])
1750
2062
  };
1751
2063
  /**
@@ -1936,6 +2248,483 @@ const DEFAULT_TRACE_ANALYST_KINDS = [
1936
2248
  IMPROVEMENT_KIND_SPEC
1937
2249
  ];
1938
2250
  //#endregion
2251
+ //#region src/feedback-trajectory-review.ts
2252
+ /** Bind an analyst finding's complete canonical JSON content to a stable digest. */
2253
+ function analystFindingDigest(finding) {
2254
+ return hashCanonical(snapshotAnalystFinding(finding, "analyst finding"));
2255
+ }
2256
+ /** Bind the complete analyst result to one immutable review target. */
2257
+ function analystRunDigest(run) {
2258
+ return hashCanonical(snapshotAnalystRun(run, "analyst run"));
2259
+ }
2260
+ function snapshotAnalystRun(value, context = "analyst run") {
2261
+ const snapshot = snapshotAnalystRunRecord(value, context);
2262
+ if (snapshot.execution_plan !== void 0) return sealExactAnalystRunReceipt(snapshot, context);
2263
+ if (snapshot.completion !== void 0) throw new TypeError(`${context} completion is valid only for an exact run`);
2264
+ return snapshot;
2265
+ }
2266
+ /** Canonicalize, validate, and deeply freeze one complete or failed exact-run receipt. */
2267
+ function snapshotExactAnalystRunReceipt(value, context = "exact analyst run receipt") {
2268
+ return sealExactAnalystRunReceipt(snapshotAnalystRunRecord(value, context), context);
2269
+ }
2270
+ function snapshotAnalystRunRecord(value, context) {
2271
+ let snapshot;
2272
+ try {
2273
+ snapshot = JSON.parse(canonicalString(value));
2274
+ } catch (cause) {
2275
+ throw new TypeError(`${context} must have a canonical JSON representation`, { cause });
2276
+ }
2277
+ if (!isRecord(snapshot)) throw new TypeError(`${context} must be an object`);
2278
+ assertOnlyKeys(snapshot, [
2279
+ "run_id",
2280
+ "correlation_id",
2281
+ "started_at",
2282
+ "ended_at",
2283
+ "findings",
2284
+ "per_analyst",
2285
+ "total_cost_usd",
2286
+ "total_cost_provenance",
2287
+ "execution_plan",
2288
+ "completion"
2289
+ ], context);
2290
+ requiredString(snapshot.run_id, `${context} run_id`);
2291
+ requiredString(snapshot.correlation_id, `${context} correlation_id`);
2292
+ canonicalTimestamp(snapshot.started_at, `${context} started_at`);
2293
+ canonicalTimestamp(snapshot.ended_at, `${context} ended_at`);
2294
+ snapshot.findings = snapshotAnalystFindings(snapshot.findings, `${context} findings`);
2295
+ if (!Array.isArray(snapshot.per_analyst)) throw new TypeError(`${context} per_analyst must be an array`);
2296
+ for (const [index, summary] of snapshot.per_analyst.entries()) assertAnalystRunSummary(summary, `${context} per_analyst ${index}`);
2297
+ if (typeof snapshot.total_cost_usd !== "number" || !Number.isFinite(snapshot.total_cost_usd) || snapshot.total_cost_usd < 0) throw new TypeError(`${context} total_cost_usd must be a finite non-negative number`);
2298
+ if (snapshot.total_cost_provenance !== void 0) assertCostProvenance(snapshot.total_cost_provenance, `${context} total_cost_provenance`);
2299
+ return snapshot;
2300
+ }
2301
+ function assertAnalystRunSummary(value, context) {
2302
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2303
+ assertOnlyKeys(value, [
2304
+ "analyst_id",
2305
+ "status",
2306
+ "reason",
2307
+ "findings_count",
2308
+ "latency_ms",
2309
+ "usage",
2310
+ "allocated_budget_usd",
2311
+ "error"
2312
+ ], context);
2313
+ requiredString(value.analyst_id, `${context} analyst_id`);
2314
+ if (value.status !== "ok" && value.status !== "skipped" && value.status !== "failed") throw new TypeError(`${context} status is invalid`);
2315
+ if (value.reason !== void 0) requiredString(value.reason, `${context} reason`);
2316
+ if (value.status === "skipped" && value.reason === void 0) throw new TypeError(`${context} skipped summary requires reason`);
2317
+ nonnegativeSafeInteger(value.findings_count, `${context} findings_count`);
2318
+ finiteNonnegative(value.latency_ms, `${context} latency_ms`);
2319
+ assertAnalystUsageReceipt(value.usage, `${context} usage`);
2320
+ if (value.allocated_budget_usd !== void 0 && value.allocated_budget_usd !== null) finiteNonnegative(value.allocated_budget_usd, `${context} allocated_budget_usd`);
2321
+ if (value.error !== void 0) {
2322
+ if (value.status !== "failed" || !isRecord(value.error)) throw new TypeError(`${context} error is valid only for failed summaries`);
2323
+ assertOnlyKeys(value.error, ["class", "message"], `${context} error`);
2324
+ requiredString(value.error.class, `${context} error class`);
2325
+ requiredString(value.error.message, `${context} error message`);
2326
+ } else if (value.status === "failed") throw new TypeError(`${context} failed summary requires error`);
2327
+ }
2328
+ function assertAnalystUsageReceipt(value, context) {
2329
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2330
+ assertOnlyKeys(value, [
2331
+ "calls",
2332
+ "tokens",
2333
+ "cost",
2334
+ "knownCostUsd"
2335
+ ], context);
2336
+ for (const field of [
2337
+ "calls",
2338
+ "tokens",
2339
+ "cost"
2340
+ ]) if (!Object.hasOwn(value, field)) throw new TypeError(`${context} ${field} is required`);
2341
+ if (value.tokens !== null) {
2342
+ if (!isRecord(value.tokens)) throw new TypeError(`${context} tokens must be an object or null`);
2343
+ assertOnlyKeys(value.tokens, [
2344
+ "input",
2345
+ "output",
2346
+ "reasoning",
2347
+ "cached",
2348
+ "cacheWrite"
2349
+ ], `${context} tokens`);
2350
+ }
2351
+ assertCostProvenance(value.cost, `${context} cost`);
2352
+ assertValidAnalystUsageReceipt(value, context);
2353
+ }
2354
+ function assertCostProvenance(value, context) {
2355
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2356
+ assertOnlyKeys(value, ["kind", "usd"], context);
2357
+ if (value.kind === "uncaptured") {
2358
+ if (value.usd !== null) throw new TypeError(`${context} uncaptured usd must be null`);
2359
+ return;
2360
+ }
2361
+ if (value.kind !== "observed" && value.kind !== "estimated") throw new TypeError(`${context} kind is invalid`);
2362
+ finiteNonnegative(value.usd, `${context} usd`);
2363
+ }
2364
+ function sealExactAnalystRunReceipt(run, context) {
2365
+ if (run.execution_plan === void 0) throw new TypeError(`${context} exact run requires execution_plan`);
2366
+ const plan = snapshotExactExecutionPlan(run.execution_plan, `${context} execution_plan`);
2367
+ const completion = snapshotExactRunCompletion(run.completion, `${context} completion`);
2368
+ run.execution_plan = plan;
2369
+ run.completion = completion;
2370
+ const summaries = run.per_analyst;
2371
+ const findings = run.findings;
2372
+ const planned = plan.analysts.map((analyst) => analyst.id);
2373
+ const completed = summaries.map((summary) => summary.analyst_id);
2374
+ if (!completed.every((analystId, index) => analystId === planned[index]) || completion.status === "complete" && completed.length !== planned.length) throw new TypeError(completion.status === "complete" ? `${context} complete receipt must contain every execution_plan analyst in exact order` : `${context} failed receipt per_analyst must be an execution_plan prefix`);
2375
+ const completedIds = new Set(completed);
2376
+ for (const finding of findings) if (!completedIds.has(finding.analyst_id)) throw new TypeError(`${context} finding names an analyst absent from per_analyst`);
2377
+ for (const summary of summaries) {
2378
+ const actual = findings.filter((finding) => finding.analyst_id === summary.analyst_id).length;
2379
+ if (summary.findings_count !== actual) throw new TypeError(`${context} findings_count does not match findings for "${summary.analyst_id}"`);
2380
+ const hasAllocation = Object.hasOwn(summary, "allocated_budget_usd");
2381
+ if (summary.status === "skipped") {
2382
+ if (hasAllocation) throw new TypeError(`${context} skipped summary "${summary.analyst_id}" cannot report an allocated budget`);
2383
+ continue;
2384
+ }
2385
+ const allocation = summary.allocated_budget_usd;
2386
+ if (!(hasAllocation && (plan.policy.budget.kind === "none" ? allocation === null : typeof allocation === "number" && plan.policy.budget.allocations_usd[summary.analyst_id] !== null && plan.policy.budget.allocations_usd[summary.analyst_id] !== void 0 && allocation <= plan.policy.budget.allocations_usd[summary.analyst_id]))) throw new TypeError(`${context} summary "${summary.analyst_id}" allocation does not match its execution plan`);
2387
+ }
2388
+ let knownCost = 0;
2389
+ for (const summary of summaries) {
2390
+ const amount = summary.usage.cost.kind === "uncaptured" ? summary.usage.knownCostUsd ?? 0 : summary.usage.cost.usd ?? 0;
2391
+ knownCost = finiteNonnegative(knownCost + amount, `${context} aggregate known cost`);
2392
+ }
2393
+ if (run.total_cost_usd !== knownCost) throw new TypeError(`${context} total_cost_usd does not match per_analyst usage`);
2394
+ if (run.total_cost_provenance === void 0) throw new TypeError(`${context} exact run requires total_cost_provenance`);
2395
+ const costs = summaries.map((summary) => summary.usage.cost);
2396
+ const expectedProvenance = costs.some((cost) => cost.kind === "uncaptured") ? {
2397
+ kind: "uncaptured",
2398
+ usd: null
2399
+ } : {
2400
+ kind: costs.some((cost) => cost.kind === "estimated") ? "estimated" : "observed",
2401
+ usd: costs.reduce((sum, cost) => finiteNonnegative(sum + (cost.usd ?? 0), `${context} aggregate captured cost`), 0)
2402
+ };
2403
+ if (hashCanonical(run.total_cost_provenance) !== hashCanonical(expectedProvenance)) throw new TypeError(`${context} total_cost_provenance does not match per_analyst usage`);
2404
+ return deepFreezeCanonicalJson(run);
2405
+ }
2406
+ function snapshotExactRunCompletion(value, context) {
2407
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2408
+ if (value.status === "complete") {
2409
+ assertOnlyKeys(value, ["status"], context);
2410
+ return value;
2411
+ }
2412
+ if (value.status !== "failed") throw new TypeError(`${context} status must be complete or failed`);
2413
+ assertOnlyKeys(value, ["status", "error"], context);
2414
+ if (!isRecord(value.error)) throw new TypeError(`${context} failed receipt requires error`);
2415
+ assertOnlyKeys(value.error, ["class", "message"], `${context} error`);
2416
+ requiredString(value.error.class, `${context} error class`);
2417
+ requiredString(value.error.message, `${context} error message`);
2418
+ return value;
2419
+ }
2420
+ function finiteNonnegative(value, context) {
2421
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) throw new TypeError(`${context} must be a non-negative finite number`);
2422
+ return value;
2423
+ }
2424
+ function nonnegativeSafeInteger(value, context) {
2425
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${context} must be a non-negative safe integer`);
2426
+ return value;
2427
+ }
2428
+ function snapshotAnalystFindings(value, context = "analyst run findings") {
2429
+ if (!Array.isArray(value)) throw new TypeError(`${context} must be an array`);
2430
+ const findings = value.map((finding, index) => snapshotAnalystFinding(finding, `${context} finding ${index}`));
2431
+ assertUniqueFindingIds(findings.map((finding) => finding.finding_id));
2432
+ return findings;
2433
+ }
2434
+ function readAnalystReview(trajectory) {
2435
+ const analystAttempts = trajectory.attempts.filter((attempt) => isRecord(attempt.artifact) && attempt.artifact.type === "analyst-run");
2436
+ const analysis = isRecord(trajectory.metadata?.analysis) ? trajectory.metadata.analysis : void 0;
2437
+ if (analystAttempts.length === 0) {
2438
+ if (analysis?.kind === "analyst-run") throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" is missing its archived run`);
2439
+ return;
2440
+ }
2441
+ if (analystAttempts.length !== 1) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" must contain exactly one archived run`);
2442
+ if (analysis?.kind !== "analyst-run") throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" is missing review state`);
2443
+ const artifact = analystAttempts[0].artifact;
2444
+ if (!isRecord(artifact)) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" has an invalid archived run`);
2445
+ const runId = requiredString(artifact.analystRunId, `analyst trajectory "${trajectory.id}" run id`);
2446
+ if (analysis.runId !== runId) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" run identity does not match its review state`);
2447
+ const artifactRunDigest = requiredDigest(artifact.runDigest, `analyst trajectory "${trajectory.id}" archived run digest`);
2448
+ const storedRunDigest = requiredDigest(analysis.runDigest, `analyst trajectory "${trajectory.id}" review run digest`);
2449
+ if (artifactRunDigest !== storedRunDigest) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" run digest does not match its review state`);
2450
+ const findings = snapshotAnalystFindings(artifact.findings, `analyst trajectory "${trajectory.id}"`);
2451
+ const findingIds = findings.map((finding) => finding.finding_id);
2452
+ const analystIds = stringArray(artifact.analystIds, `analyst trajectory "${trajectory.id}" analyst ids`);
2453
+ const attemptMetadata = analystAttempts[0].metadata;
2454
+ if (!isRecord(attemptMetadata)) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" is missing archived run metadata`);
2455
+ const archivedRun = snapshotAnalystRun({
2456
+ run_id: runId,
2457
+ correlation_id: artifact.correlationId,
2458
+ started_at: analysis.startedAt,
2459
+ ended_at: analysis.endedAt,
2460
+ findings,
2461
+ per_analyst: attemptMetadata.perAnalyst,
2462
+ total_cost_usd: analysis.knownCostUsd,
2463
+ ...analysis.costProvenance === void 0 ? {} : { total_cost_provenance: analysis.costProvenance },
2464
+ ...artifact.executionPlan === void 0 ? {} : {
2465
+ execution_plan: artifact.executionPlan,
2466
+ completion: artifact.completion
2467
+ }
2468
+ }, `analyst trajectory "${trajectory.id}" archived run`);
2469
+ const knownAnalystIds = new Set(analystIds);
2470
+ for (const [index, finding] of findings.entries()) if (!knownAnalystIds.has(finding.analyst_id)) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" omits generating analyst "${finding.analyst_id}" at finding ${index}`);
2471
+ const reviewDecisions = validateAnalystReviewDecisions({
2472
+ runId,
2473
+ runDigest: storedRunDigest,
2474
+ findings,
2475
+ analystIds,
2476
+ decisions: analysis.reviewDecisions,
2477
+ requireComplete: true
2478
+ });
2479
+ const expectedRunDigest = analystRunDigest(archivedRun);
2480
+ if (storedRunDigest !== expectedRunDigest) throw new TypeError(`feedbackTrajectoryToOptimizerRow: analyst trajectory "${trajectory.id}" archived run digest mismatch`);
2481
+ return {
2482
+ runId,
2483
+ runDigest: expectedRunDigest,
2484
+ findings,
2485
+ findingIds,
2486
+ analystIds,
2487
+ reviewDecisions
2488
+ };
2489
+ }
2490
+ function completedAnalystReviewQuality(review) {
2491
+ const findingDecisions = review.reviewDecisions.filter((decision) => decision.verdict !== "completeness_assessed");
2492
+ const completeness = review.reviewDecisions.filter((decision) => decision.verdict === "completeness_assessed");
2493
+ if (completeness.length !== 1) throw new TypeError("feedbackTrajectoryToOptimizerRow: analyst run requires exactly one independent completeness_assessed decision");
2494
+ const confirmed = findingDecisions.filter((decision) => decision.verdict === "confirmed").length;
2495
+ const rejected = findingDecisions.length - confirmed;
2496
+ const emitted = review.findingIds.length;
2497
+ const missed = completeness[0].missedIssues.length;
2498
+ const precision = emitted === 0 ? 1 : confirmed / emitted;
2499
+ const recallDenominator = confirmed + missed;
2500
+ const recall = recallDenominator === 0 ? 1 : confirmed / recallDenominator;
2501
+ return {
2502
+ precision,
2503
+ recall,
2504
+ f1: precision + recall === 0 ? 0 : 2 * precision * recall / (precision + recall),
2505
+ counts: {
2506
+ emitted,
2507
+ confirmed,
2508
+ rejected,
2509
+ missed
2510
+ }
2511
+ };
2512
+ }
2513
+ function validateAnalystReviewDecisions(input) {
2514
+ if (!Array.isArray(input.decisions)) throw new TypeError("analyst review decisions must be an array");
2515
+ const findings = snapshotAnalystFindings(input.findings);
2516
+ const expectedRunDigest = requiredDigest(input.runDigest, "analyst review run digest");
2517
+ const findingsById = new Map(findings.map((finding) => [finding.finding_id, finding]));
2518
+ const generatingAnalystIds = new Set(input.analystIds);
2519
+ const seenFindingIds = /* @__PURE__ */ new Set();
2520
+ let completenessCount = 0;
2521
+ const decisions = input.decisions.map((value, index) => {
2522
+ if (!isRecord(value)) throw new TypeError(`analyst review decision ${index} must be an object`);
2523
+ const source = requiredString(value.source, `analyst review decision ${index} source`);
2524
+ if (!isAnalystReviewSource(source)) throw new TypeError(`analyst review decision ${index} source must be user, judge, environment, metric, or policy`);
2525
+ const reviewerId = requiredString(value.reviewerId, `analyst review decision ${index} reviewerId`);
2526
+ if (generatingAnalystIds.has(reviewerId)) throw new TypeError(`analyst review decision ${index} reviewerId must differ from the generating analyst`);
2527
+ const reviewId = requiredString(value.reviewId, `analyst review decision ${index} reviewId`);
2528
+ if (reviewId === input.runId) throw new TypeError(`analyst review decision ${index} reviewId must identify an independent review`);
2529
+ const reason = requiredString(value.reason, `analyst review decision ${index} reason`);
2530
+ const decidedAt = canonicalTimestamp(value.decidedAt, `analyst review decision ${index} decidedAt`);
2531
+ const runDigest = requiredDigest(value.runDigest, `analyst review decision ${index} runDigest`);
2532
+ if (runDigest !== expectedRunDigest) throw new TypeError(`analyst review decision ${index} run digest mismatch`);
2533
+ if (value.verdict === "completeness_assessed") {
2534
+ assertOnlyKeys(value, [
2535
+ "runDigest",
2536
+ "verdict",
2537
+ "missedIssues",
2538
+ "source",
2539
+ "reviewerId",
2540
+ "reviewId",
2541
+ "reason",
2542
+ "decidedAt"
2543
+ ], `analyst review decision ${index}`);
2544
+ completenessCount += 1;
2545
+ if (completenessCount > 1) throw new TypeError("duplicate completeness_assessed analyst review decision");
2546
+ return {
2547
+ runDigest,
2548
+ verdict: "completeness_assessed",
2549
+ missedIssues: validateMissedIssues(value.missedIssues, findingsById, `analyst review decision ${index}`),
2550
+ source,
2551
+ reviewerId,
2552
+ reviewId,
2553
+ reason,
2554
+ decidedAt
2555
+ };
2556
+ }
2557
+ if (value.verdict !== "confirmed" && value.verdict !== "rejected") throw new TypeError(`analyst review decision ${index} verdict must be confirmed, rejected, or completeness_assessed`);
2558
+ assertOnlyKeys(value, [
2559
+ "runDigest",
2560
+ "findingId",
2561
+ "findingDigest",
2562
+ "verdict",
2563
+ "source",
2564
+ "reviewerId",
2565
+ "reviewId",
2566
+ "reason",
2567
+ "decidedAt"
2568
+ ], `analyst review decision ${index}`);
2569
+ const findingId = requiredString(value.findingId, `analyst review decision ${index} findingId`);
2570
+ const finding = findingsById.get(findingId);
2571
+ if (!finding) throw new TypeError(`analyst review decision references unknown finding id "${findingId}"`);
2572
+ if (seenFindingIds.has(findingId)) throw new TypeError(`duplicate analyst review decision for finding id "${findingId}"`);
2573
+ seenFindingIds.add(findingId);
2574
+ const findingDigest = requiredString(value.findingDigest, `analyst review decision ${index} findingDigest`);
2575
+ const expectedDigest = analystFindingDigest(finding);
2576
+ if (findingDigest !== expectedDigest) throw new TypeError(`analyst review decision ${index} digest mismatch for finding id "${findingId}"`);
2577
+ return {
2578
+ runDigest,
2579
+ findingId,
2580
+ findingDigest: expectedDigest,
2581
+ verdict: value.verdict,
2582
+ source,
2583
+ reviewerId,
2584
+ reviewId,
2585
+ reason,
2586
+ decidedAt
2587
+ };
2588
+ });
2589
+ if (input.requireComplete) {
2590
+ const missing = findings.map((finding) => finding.finding_id).filter((findingId) => !seenFindingIds.has(findingId));
2591
+ if (missing.length > 0) throw new TypeError(`feedbackTrajectoryToOptimizerRow: missing independent decisions for finding ids: ${missing.join(", ")}`);
2592
+ if (completenessCount !== 1) throw new TypeError("feedbackTrajectoryToOptimizerRow: analyst run requires exactly one independent completeness_assessed decision");
2593
+ }
2594
+ return decisions;
2595
+ }
2596
+ function assertUniqueFindingIds(findingIds) {
2597
+ const seen = /* @__PURE__ */ new Set();
2598
+ for (const findingId of findingIds) {
2599
+ if (findingId.trim().length === 0) throw new TypeError("analyst finding id must not be empty");
2600
+ if (seen.has(findingId)) throw new TypeError(`analyst run contains duplicate finding id "${findingId}"`);
2601
+ seen.add(findingId);
2602
+ }
2603
+ }
2604
+ function snapshotAnalystFinding(value, context) {
2605
+ let snapshot;
2606
+ try {
2607
+ snapshot = JSON.parse(canonicalString(value));
2608
+ } catch (cause) {
2609
+ throw new TypeError(`${context} must have a canonical JSON representation`, { cause });
2610
+ }
2611
+ assertAnalystFinding(snapshot, context);
2612
+ return snapshot;
2613
+ }
2614
+ function assertAnalystFinding(value, context) {
2615
+ if (!isRecord(value)) throw new TypeError(`${context} must be an object`);
2616
+ assertOnlyKeys(value, [
2617
+ "schema_version",
2618
+ "finding_id",
2619
+ "analyst_id",
2620
+ "produced_at",
2621
+ "severity",
2622
+ "area",
2623
+ "claim",
2624
+ "rationale",
2625
+ "evidence_refs",
2626
+ "recommended_action",
2627
+ "validation_plan",
2628
+ "confidence",
2629
+ "subject",
2630
+ "derived_from_judge",
2631
+ "metadata"
2632
+ ], context);
2633
+ if (value.schema_version !== "1.0.0") throw new TypeError(`${context} schema_version must be "1.0.0"`);
2634
+ requiredString(value.finding_id, `${context} finding_id`);
2635
+ requiredString(value.analyst_id, `${context} analyst_id`);
2636
+ canonicalTimestamp(value.produced_at, `${context} produced_at`);
2637
+ if (value.severity !== "critical" && value.severity !== "high" && value.severity !== "medium" && value.severity !== "low" && value.severity !== "info") throw new TypeError(`${context} severity is invalid`);
2638
+ requiredString(value.area, `${context} area`);
2639
+ requiredString(value.claim, `${context} claim`);
2640
+ optionalString(value.rationale, `${context} rationale`);
2641
+ value.evidence_refs = validateEvidenceRefs(value.evidence_refs, `${context} evidence_refs`);
2642
+ optionalString(value.recommended_action, `${context} recommended_action`);
2643
+ optionalString(value.validation_plan, `${context} validation_plan`);
2644
+ if (typeof value.confidence !== "number" || !Number.isFinite(value.confidence) || value.confidence < 0 || value.confidence > 1) throw new TypeError(`${context} confidence must be a finite number from 0 through 1`);
2645
+ optionalString(value.subject, `${context} subject`);
2646
+ if (value.derived_from_judge !== void 0 && typeof value.derived_from_judge !== "boolean") throw new TypeError(`${context} derived_from_judge must be a boolean`);
2647
+ if (value.metadata !== void 0 && !isRecord(value.metadata)) throw new TypeError(`${context} metadata must be an object`);
2648
+ }
2649
+ function validateMissedIssues(value, findingsById, context) {
2650
+ if (!Array.isArray(value)) throw new TypeError(`${context} missedIssues must be an array`);
2651
+ const seen = /* @__PURE__ */ new Set();
2652
+ return value.map((issue, index) => {
2653
+ const issueContext = `${context} missedIssues ${index}`;
2654
+ if (!isRecord(issue)) throw new TypeError(`${issueContext} must be an object`);
2655
+ assertOnlyKeys(issue, [
2656
+ "id",
2657
+ "reason",
2658
+ "evidence"
2659
+ ], issueContext);
2660
+ const id = requiredString(issue.id, `${issueContext} id`);
2661
+ if (findingsById.has(id)) throw new TypeError(`${issueContext} id "${id}" is already an emitted finding id`);
2662
+ if (seen.has(id)) throw new TypeError(`duplicate missed issue id "${id}"`);
2663
+ seen.add(id);
2664
+ return {
2665
+ id,
2666
+ reason: requiredString(issue.reason, `${issueContext} reason`),
2667
+ ...issue.evidence === void 0 ? {} : { evidence: validateEvidenceRefs(issue.evidence, `${issueContext} evidence`) }
2668
+ };
2669
+ });
2670
+ }
2671
+ function validateEvidenceRefs(value, context) {
2672
+ if (!Array.isArray(value)) throw new TypeError(`${context} must be an array`);
2673
+ return value.map((evidence, index) => {
2674
+ const evidenceContext = `${context} ${index}`;
2675
+ if (!isRecord(evidence)) throw new TypeError(`${evidenceContext} must be an object`);
2676
+ assertOnlyKeys(evidence, [
2677
+ "kind",
2678
+ "uri",
2679
+ "excerpt"
2680
+ ], evidenceContext);
2681
+ if (evidence.kind !== "span" && evidence.kind !== "event" && evidence.kind !== "artifact" && evidence.kind !== "finding" && evidence.kind !== "metric") throw new TypeError(`${evidenceContext} kind is invalid`);
2682
+ const uri = requiredString(evidence.uri, `${evidenceContext} uri`);
2683
+ const excerpt = evidence.excerpt;
2684
+ optionalString(excerpt, `${evidenceContext} excerpt`);
2685
+ return {
2686
+ kind: evidence.kind,
2687
+ uri,
2688
+ ...excerpt === void 0 ? {} : { excerpt }
2689
+ };
2690
+ });
2691
+ }
2692
+ function assertOnlyKeys(value, allowed, name) {
2693
+ const allowedKeys = new Set(allowed);
2694
+ const unexpected = Object.keys(value).filter((key) => !allowedKeys.has(key));
2695
+ if (unexpected.length > 0) throw new TypeError(`${name} contains unknown fields: ${unexpected.sort().join(", ")}`);
2696
+ }
2697
+ function stringArray(value, name) {
2698
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new TypeError(`${name} must be an array of strings`);
2699
+ const strings = value.map((item) => requiredString(item, name));
2700
+ if (new Set(strings).size !== strings.length) throw new TypeError(`${name} must contain unique values`);
2701
+ return strings;
2702
+ }
2703
+ function requiredString(value, name) {
2704
+ if (typeof value !== "string" || value.trim().length === 0) throw new TypeError(`${name} must be a non-empty string`);
2705
+ return value;
2706
+ }
2707
+ function requiredDigest(value, name) {
2708
+ const digest = requiredString(value, name);
2709
+ if (!/^sha256:[a-f0-9]{64}$/.test(digest)) throw new TypeError(`${name} must be a sha256 digest`);
2710
+ return digest;
2711
+ }
2712
+ function optionalString(value, name) {
2713
+ if (value !== void 0 && typeof value !== "string") throw new TypeError(`${name} must be a string`);
2714
+ }
2715
+ function canonicalTimestamp(value, name) {
2716
+ const timestamp = requiredString(value, name);
2717
+ const parsed = new Date(timestamp);
2718
+ if (Number.isNaN(parsed.valueOf()) || parsed.toISOString() !== timestamp) throw new TypeError(`${name} must be a canonical ISO 8601 UTC timestamp`);
2719
+ return timestamp;
2720
+ }
2721
+ function isAnalystReviewSource(value) {
2722
+ return value === "user" || value === "judge" || value === "environment" || value === "metric" || value === "policy";
2723
+ }
2724
+ function isRecord(value) {
2725
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2726
+ }
2727
+ //#endregion
1939
2728
  //#region src/analyst/registry.ts
1940
2729
  /**
1941
2730
  * AnalystRegistry — orchestrate N analysts against one run.
@@ -1954,6 +2743,17 @@ const DEFAULT_TRACE_ANALYST_KINDS = [
1954
2743
  * (equal split vs weighted vs custom) lives in `BudgetPolicy`. Both
1955
2744
  * have sensible defaults; consumers override only what they need.
1956
2745
  */
2746
+ /** A post-start exact-run failure; completed work remains attached for accounting and review. */
2747
+ var ExactAnalystRunExecutionError = class extends Error {
2748
+ name = "ExactAnalystRunExecutionError";
2749
+ result;
2750
+ constructor(message, result, options) {
2751
+ super(message, options);
2752
+ const snapshot = snapshotExactAnalystRunReceipt(result, "ExactAnalystRunExecutionError result");
2753
+ if (snapshot.completion.status !== "failed") throw new TypeError("ExactAnalystRunExecutionError result must be a failed receipt");
2754
+ this.result = snapshot;
2755
+ }
2756
+ };
1957
2757
  var AnalystRegistry = class {
1958
2758
  analysts = /* @__PURE__ */ new Map();
1959
2759
  options;
@@ -1961,12 +2761,15 @@ var AnalystRegistry = class {
1961
2761
  this.options = options;
1962
2762
  }
1963
2763
  register(analyst) {
1964
- if (!analyst.id) throw new Error("AnalystRegistry.register: analyst.id is required");
1965
- if (this.analysts.has(analyst.id)) throw new Error(`AnalystRegistry.register: duplicate analyst id "${analyst.id}"`);
1966
- if (!analyst.version) throw new Error(`AnalystRegistry.register: analyst "${analyst.id}" must declare a version`);
1967
- if (analyst.cost.kind === "deterministic" && analyst.cost.settlement_timeout_ms !== void 0) throw new TypeError(`AnalystRegistry.register: deterministic analyst "${analyst.id}" cannot declare settlement_timeout_ms`);
1968
- if (analyst.cost.settlement_timeout_ms !== void 0) validateUsageSettlementTimeout(analyst.cost.settlement_timeout_ms);
1969
- this.analysts.set(analyst.id, analyst);
2764
+ const id = analyst.id;
2765
+ const version = analyst.version;
2766
+ const cost = analyst.cost;
2767
+ if (!id) throw new Error("AnalystRegistry.register: analyst.id is required");
2768
+ if (this.analysts.has(id)) throw new Error(`AnalystRegistry.register: duplicate analyst id "${id}"`);
2769
+ if (!version) throw new Error(`AnalystRegistry.register: analyst "${id}" must declare a version`);
2770
+ if (cost.kind === "deterministic" && cost.settlement_timeout_ms !== void 0) throw new TypeError(`AnalystRegistry.register: deterministic analyst "${id}" cannot declare settlement_timeout_ms`);
2771
+ if (cost.settlement_timeout_ms !== void 0) validateUsageSettlementTimeout(cost.settlement_timeout_ms);
2772
+ this.analysts.set(id, analyst);
1970
2773
  }
1971
2774
  list() {
1972
2775
  return Array.from(this.analysts.values()).map((a) => ({
@@ -1980,6 +2783,15 @@ var AnalystRegistry = class {
1980
2783
  for await (const ev of this.runStream(runId, inputs, runOpts)) if (ev.type === "run-completed") return ev.result;
1981
2784
  throw new Error("AnalystRegistry.run: stream completed without run-completed event");
1982
2785
  }
2786
+ /** Run exactly the ordered analysts and complete policy supplied by the caller. */
2787
+ async runExact(runId, inputs, runOpts) {
2788
+ for await (const ev of this.runExactStream(runId, inputs, runOpts)) if (ev.type === "run-completed") return ev.result;
2789
+ throw new Error("AnalystRegistry.runExact: stream completed without run-completed event");
2790
+ }
2791
+ /** Streaming counterpart to {@link runExact}. */
2792
+ async *runExactStream(runId, inputs, runOpts) {
2793
+ for await (const event of this.executePlanStream(this.normalizeExactPlan(runId, inputs, runOpts))) yield event;
2794
+ }
1983
2795
  /**
1984
2796
  * Streaming counterpart to `run()`. Emits `AnalystRunEvent` values
1985
2797
  * in real time — `run-started`, then per-analyst `skipped` /
@@ -1992,50 +2804,124 @@ var AnalystRegistry = class {
1992
2804
  * replacement.
1993
2805
  */
1994
2806
  async *runStream(runId, inputs, runOpts = {}) {
2807
+ yield* this.executePlanStream(this.normalizeLegacyPlan(runId, inputs, runOpts));
2808
+ }
2809
+ normalizeLegacyPlan(runId, inputs, runOpts) {
2810
+ const timeoutMs = validateTimeout(runOpts.timeoutMs) ?? null;
2811
+ const budget = runOpts.budget ?? this.options.defaultBudget;
2812
+ validateBudgetPolicy(budget);
2813
+ return {
2814
+ runId,
2815
+ prepared: this.selectAnalysts(runOpts).map((analyst) => ({
2816
+ analyst,
2817
+ input: this.routeInput(analyst, inputs)
2818
+ })),
2819
+ budget: budget ? {
2820
+ kind: "dynamic",
2821
+ policy: budget
2822
+ } : { kind: "none" },
2823
+ totalTimeoutMs: timeoutMs,
2824
+ signal: runOpts.signal ?? null,
2825
+ costLedger: runOpts.costLedger ?? null,
2826
+ costPhase: runOpts.costPhase ?? null,
2827
+ tags: runOpts.tags ?? null,
2828
+ priorFindings: runOpts.priorFindings ?? null,
2829
+ chainFindings: runOpts.chainFindings ?? false,
2830
+ hooks: this.options.hooks ?? {},
2831
+ chat: this.options.chat,
2832
+ log: this.options.log ?? (() => {}),
2833
+ executionSnapshot: void 0
2834
+ };
2835
+ }
2836
+ normalizeExactPlan(runId, inputs, runOpts) {
2837
+ const exactRunId = snapshotExactRunId(runId);
2838
+ const exact = snapshotExactRegistryRunOpts(runOpts);
2839
+ const selected = normalizeExactAnalysts(this.selectExactAnalysts(exact.analystIds));
2840
+ const registryChat = this.options.chat;
2841
+ const registryChatIdentity = this.options.chatIdentity;
2842
+ const registryHooks = this.options.hooks;
2843
+ const registryHooksIdentity = this.options.hooksIdentity;
2844
+ if (exact.useRegistryChat && registryChat === void 0) throw new TypeError("ExactRegistryRunOpts.useRegistryChat is true but the registry has no chat client");
2845
+ if (exact.applyRegistryHooks && !hasRegistryHooks(registryHooks)) throw new TypeError("ExactRegistryRunOpts.applyRegistryHooks is true but the registry has no lifecycle hooks");
2846
+ const inputSnapshot = snapshotAnalystRunInputChannels(inputs);
2847
+ const prepared = selected.map((analyst) => ({
2848
+ analyst,
2849
+ input: this.routeInput(analyst, inputSnapshot)
2850
+ }));
2851
+ if (exact.missingInputMode === "abort") {
2852
+ const missing = prepared.find((candidate) => candidate.input.kind === "missing")?.analyst;
2853
+ if (missing) throw new TypeError(`ExactRegistryRunOpts.missingInputMode abort preflight found no "${missing.inputKind}" input for "${missing.id}"`);
2854
+ }
2855
+ const hooksIdentity = exact.applyRegistryHooks && registryHooks ? requireExactComponentIdentity(registryHooksIdentity, "registry hooks") : null;
2856
+ const chatIdentity = exact.useRegistryChat ? requireExactComponentIdentity(registryChatIdentity, "registry chat") : null;
2857
+ const costLedgerIdentity = exact.costLedger === null ? null : requireExactComponentIdentity(exact.costLedgerIdentity ?? void 0, "cost ledger");
2858
+ const executionSnapshot = exactExecutionSnapshot(selected, exact, exactFixedBudgets(exact.budget, prepared.filter((candidate) => candidate.input.kind === "present").map((candidate) => candidate.analyst), selected), costLedgerIdentity, hooksIdentity, chatIdentity);
2859
+ return {
2860
+ runId: exactRunId,
2861
+ prepared,
2862
+ budget: { kind: "none" },
2863
+ totalTimeoutMs: exact.totalTimeoutMs,
2864
+ signal: exact.signal,
2865
+ costLedger: exact.costLedger,
2866
+ costPhase: exact.costPhase,
2867
+ tags: exact.tags,
2868
+ priorFindings: exact.priorFindings,
2869
+ chainFindings: exact.chainFindings,
2870
+ hooks: exact.applyRegistryHooks && registryHooks ? snapshotHooks(registryHooks) : {},
2871
+ chat: exact.useRegistryChat && registryChat ? snapshotChat(registryChat) : void 0,
2872
+ log: () => {},
2873
+ executionSnapshot
2874
+ };
2875
+ }
2876
+ async *executePlanStream(plan) {
2877
+ const exact = plan.executionSnapshot !== void 0;
2878
+ if (exact && plan.signal?.aborted) throw abortReason(plan.signal);
1995
2879
  const correlationId = `ar_${randomUUID().slice(0, 12)}`;
1996
- const log = this.options.log ?? (() => {});
1997
- const hooks = this.options.hooks ?? {};
2880
+ const log = plan.log;
1998
2881
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
1999
2882
  const started = Date.now();
2000
- const timeoutMs = validateTimeout(runOpts.timeoutMs);
2001
- const deadlineMs = timeoutMs === void 0 ? void 0 : started + timeoutMs;
2002
- const timeoutSignal = timeoutMs === void 0 ? void 0 : AbortSignal.timeout(timeoutMs);
2003
- const runSignal = combineAbortSignals(runOpts.signal, timeoutSignal);
2004
- const selected = this.selectAnalysts(runOpts);
2005
- const budget = runOpts.budget ?? this.options.defaultBudget;
2006
- validateBudgetPolicy(budget);
2007
- yield {
2883
+ const timeoutSignal = plan.totalTimeoutMs === null ? void 0 : AbortSignal.timeout(plan.totalTimeoutMs);
2884
+ const runSignal = combineAbortSignals(plan.signal ?? void 0, timeoutSignal);
2885
+ const deadlineMs = plan.totalTimeoutMs === null ? void 0 : started + plan.totalTimeoutMs;
2886
+ const runnable = plan.prepared.filter((candidate) => candidate.input.kind === "present").map((candidate) => candidate.analyst);
2887
+ let remainingUsd = plan.budget.kind === "dynamic" ? plan.budget.policy.totalUsd : void 0;
2888
+ const weights = plan.budget.kind === "dynamic" ? plan.budget.policy.weights : void 0;
2889
+ const totalWeight = weights && plan.budget.kind === "dynamic" && plan.budget.policy.totalUsd != null && !plan.budget.policy.allocate && runnable.length > 0 ? runnable.reduce((sum, analyst) => sum + analystWeight(weights, analyst.id), 0) : void 0;
2890
+ if (totalWeight === 0) throw new Error("BudgetPolicy.weights must allocate positive weight to a runnable analyst");
2891
+ const upstreamFindings = [];
2892
+ yield snapshotExecutionEvent({
2008
2893
  type: "run-started",
2009
- run_id: runId,
2894
+ run_id: plan.runId,
2010
2895
  correlation_id: correlationId,
2011
2896
  started_at: startedAt,
2012
- analyst_ids: selected.map((a) => a.id)
2013
- };
2014
- const summaries = [];
2015
- const allFindings = [];
2016
- let totalCost = 0;
2017
- let remainingUsd = budget?.totalUsd;
2018
- const runnableAnalysts = selected.filter((a) => this.routeInput(a, inputs).kind !== "missing");
2019
- const runnableCount = runnableAnalysts.length;
2020
- const weights = budget?.weights;
2021
- const totalWeight = weights && budget?.totalUsd != null && !budget.allocate && runnableCount > 0 ? runnableAnalysts.reduce((sum, analyst) => sum + analystWeight(weights, analyst.id), 0) : void 0;
2022
- if (totalWeight === 0) throw new Error("BudgetPolicy.weights must allocate positive weight to a runnable analyst");
2023
- for (const analyst of selected) {
2897
+ analyst_ids: plan.prepared.map(({ analyst }) => analyst.id),
2898
+ ...plan.executionSnapshot === void 0 ? {} : { execution_plan: plan.executionSnapshot }
2899
+ }, exact);
2900
+ const executions = [];
2901
+ let executionFailure;
2902
+ for (const { analyst, input } of plan.prepared) {
2024
2903
  const t0 = Date.now();
2025
2904
  if (runSignal?.aborted) {
2026
2905
  const summary = abortedBeforeStartSummary(analyst, runSignal);
2027
- summaries.push(summary);
2906
+ executions.push({
2907
+ summary,
2908
+ findings: [],
2909
+ budgetDebitUsd: 0
2910
+ });
2028
2911
  log(`[analyst] skip ${analyst.id} — run aborted`, {
2029
- runId,
2912
+ runId: plan.runId,
2030
2913
  reason: summary.reason
2031
2914
  });
2032
- yield {
2915
+ yield snapshotExecutionEvent({
2033
2916
  type: "analyst-skipped",
2034
2917
  summary
2035
- };
2918
+ }, exact);
2919
+ if (exact) {
2920
+ executionFailure = abortReason(runSignal);
2921
+ break;
2922
+ }
2036
2923
  continue;
2037
2924
  }
2038
- const input = this.routeInput(analyst, inputs);
2039
2925
  if (input.kind === "missing") {
2040
2926
  const summary = {
2041
2927
  analyst_id: analyst.id,
@@ -2045,189 +2931,295 @@ var AnalystRegistry = class {
2045
2931
  latency_ms: 0,
2046
2932
  usage: zeroUsage()
2047
2933
  };
2048
- summaries.push(summary);
2934
+ const execution = {
2935
+ summary,
2936
+ findings: [],
2937
+ budgetDebitUsd: 0
2938
+ };
2939
+ executions.push(execution);
2049
2940
  log(`[analyst] skip ${analyst.id} — missing input`, {
2050
- runId,
2941
+ runId: plan.runId,
2051
2942
  kind: analyst.inputKind
2052
2943
  });
2053
- await waitForHook(hooks.onAfterAnalyze ? () => hooks.onAfterAnalyze?.({
2054
- analyst,
2055
- summary,
2056
- findings: [],
2057
- runId
2058
- }) : void 0, runSignal);
2059
- yield {
2944
+ const hookValues = snapshotAfterHookValues(summary, [], exact);
2945
+ try {
2946
+ await waitForHook(plan.hooks.onAfterAnalyze ? () => plan.hooks.onAfterAnalyze?.({
2947
+ analyst,
2948
+ summary: hookValues.summary,
2949
+ findings: hookValues.findings,
2950
+ runId: plan.runId
2951
+ }) : void 0, runSignal);
2952
+ } catch (error) {
2953
+ if (!exact) throw error;
2954
+ executionFailure = error;
2955
+ }
2956
+ yield snapshotExecutionEvent({
2060
2957
  type: "analyst-skipped",
2061
2958
  summary
2062
- };
2959
+ }, exact);
2960
+ if (executionFailure !== void 0) break;
2063
2961
  continue;
2064
2962
  }
2065
- const perBudget = allocateBudget(budget, {
2963
+ const allocatedUsd = plan.executionSnapshot === void 0 ? allocateBudget(plan.budget.kind === "dynamic" ? plan.budget.policy : void 0, {
2066
2964
  analyst,
2067
2965
  remainingUsd,
2068
- runningCount: runnableCount,
2966
+ runningCount: runnable.length,
2069
2967
  totalWeight
2070
- });
2968
+ }) : exactPlannedAllocation(plan.executionSnapshot, analyst.id);
2969
+ const budgetCeilingUsd = plan.executionSnapshot === void 0 ? remainingUsd : allocatedUsd;
2071
2970
  const usageReceipts = [];
2971
+ const contextTags = plan.tags === null ? void 0 : { ...plan.tags };
2972
+ const priorFindings = selectPriorFindings(plan.priorFindings ?? void 0, analyst.id);
2973
+ const chainedFindings = plan.chainFindings && upstreamFindings.length > 0 ? [...upstreamFindings] : void 0;
2072
2974
  const ctx = {
2073
- runId,
2975
+ runId: plan.runId,
2074
2976
  correlationId,
2075
2977
  deadlineMs,
2076
- budgetUsd: perBudget,
2077
- costLedger: runOpts.costLedger,
2078
- costPhase: runOpts.costPhase,
2079
- chat: this.options.chat,
2080
- tags: runOpts.tags,
2081
- log: (msg, fields) => log(`[${analyst.id}] ${msg}`, {
2082
- runId,
2978
+ budgetUsd: allocatedUsd,
2979
+ costLedger: plan.costLedger ?? void 0,
2980
+ costPhase: plan.costPhase ?? void 0,
2981
+ chat: plan.chat,
2982
+ tags: contextTags,
2983
+ log: (message, fields) => log(`[${analyst.id}] ${message}`, {
2984
+ runId: plan.runId,
2083
2985
  correlationId,
2084
2986
  ...fields
2085
2987
  }),
2086
2988
  signal: runSignal,
2087
- priorFindings: selectPriorFindings(runOpts.priorFindings, analyst.id),
2088
- upstreamFindings: runOpts.chainFindings && allFindings.length > 0 ? [...allFindings] : void 0,
2989
+ priorFindings,
2990
+ upstreamFindings: chainedFindings,
2089
2991
  recordUsage: (receipt) => {
2090
- assertValidUsageReceipt(receipt);
2091
- usageReceipts.push(receipt);
2992
+ if (!exact) {
2993
+ assertValidAnalystUsageReceipt(receipt);
2994
+ usageReceipts.push(receipt);
2995
+ return;
2996
+ }
2997
+ usageReceipts.push(snapshotUsageReceiptOnce(receipt, `AnalystRegistry.runExact analyst "${analyst.id}" usage`));
2092
2998
  }
2093
2999
  };
2094
- await waitForHook(hooks.onBeforeAnalyze ? () => hooks.onBeforeAnalyze?.({
2095
- analyst,
2096
- ctx,
2097
- runId
2098
- }) : void 0, runSignal);
3000
+ if (exact) {
3001
+ if (contextTags) deepFreezeCanonicalJson(contextTags);
3002
+ if (priorFindings) deepFreezeCanonicalJson(priorFindings);
3003
+ if (chainedFindings) deepFreezeCanonicalJson(chainedFindings);
3004
+ Object.freeze(ctx);
3005
+ }
3006
+ try {
3007
+ await waitForHook(plan.hooks.onBeforeAnalyze ? () => plan.hooks.onBeforeAnalyze?.({
3008
+ analyst,
3009
+ ctx,
3010
+ runId: plan.runId
3011
+ }) : void 0, runSignal);
3012
+ } catch (error) {
3013
+ if (!exact) throw error;
3014
+ executionFailure = error;
3015
+ break;
3016
+ }
2099
3017
  if (runSignal?.aborted) {
2100
3018
  const summary = abortedBeforeStartSummary(analyst, runSignal, Date.now() - t0);
2101
- summaries.push(summary);
2102
- log(`[analyst] skip ${analyst.id} — run aborted`, {
2103
- runId,
2104
- reason: summary.reason
3019
+ executions.push({
3020
+ summary,
3021
+ findings: [],
3022
+ budgetDebitUsd: 0
2105
3023
  });
2106
- yield {
3024
+ yield snapshotExecutionEvent({
2107
3025
  type: "analyst-skipped",
2108
3026
  summary
2109
- };
3027
+ }, exact);
3028
+ if (exact) {
3029
+ executionFailure = abortReason(runSignal);
3030
+ break;
3031
+ }
2110
3032
  continue;
2111
3033
  }
2112
- const effectiveBudget = validateEffectiveBudget(ctx.budgetUsd, remainingUsd, analyst.id);
2113
- yield {
3034
+ let effectiveBudget;
3035
+ try {
3036
+ effectiveBudget = validateEffectiveBudget(ctx.budgetUsd, budgetCeilingUsd, analyst.id);
3037
+ } catch (error) {
3038
+ if (!exact) throw error;
3039
+ executionFailure = error;
3040
+ break;
3041
+ }
3042
+ const analystContext = exact ? ctx : { ...ctx };
3043
+ const executionSignal = exact ? ctx.signal : runSignal;
3044
+ yield snapshotExecutionEvent({
2114
3045
  type: "analyst-started",
2115
3046
  analyst_id: analyst.id,
2116
3047
  started_at: new Date(t0).toISOString()
2117
- };
3048
+ }, exact);
2118
3049
  let findings;
2119
3050
  let summary;
3051
+ let lifecycleFailure;
3052
+ let analysisFailure;
2120
3053
  try {
2121
3054
  if (runSignal?.aborted) throw abortReason(runSignal);
2122
- findings = await waitForOperation(analyst.analyze(input.value, ctx), runSignal, analystAbortGraceMs(analyst));
2123
- const latency = Date.now() - t0;
2124
- const usage = resolveUsage(analyst, usageReceipts);
2125
- const cost = knownCostUsd(usage);
2126
- totalCost += cost;
2127
- if (typeof remainingUsd === "number") remainingUsd = Math.max(0, remainingUsd - budgetDebit(usage, effectiveBudget));
2128
- allFindings.push(...findings);
3055
+ findings = snapshotExecutionFindings(await waitForOperation(analyst.analyze(input.value, analystContext), executionSignal, analystAbortGraceMs(analyst)), exact, `AnalystRegistry.runExact analyst "${analyst.id}" findings`);
3056
+ } catch (error) {
3057
+ const cause = error instanceof Error ? error : new Error(String(error));
3058
+ analysisFailure = cause;
3059
+ let hookFindings = [];
3060
+ if (!executionSignal?.aborted) try {
3061
+ hookFindings = snapshotExecutionFindings(await waitForHook(plan.hooks.onError ? () => plan.hooks.onError?.({
3062
+ analyst,
3063
+ error: cause,
3064
+ runId: plan.runId
3065
+ }) : void 0, executionSignal) ?? [], exact, `AnalystRegistry.runExact analyst "${analyst.id}" onError findings`);
3066
+ } catch (error) {
3067
+ lifecycleFailure = error;
3068
+ }
3069
+ findings = hookFindings;
3070
+ }
3071
+ let usage;
3072
+ try {
3073
+ usage = resolveUsage(analyst, usageReceipts, exact);
3074
+ } catch (error) {
3075
+ if (!exact) throw error;
3076
+ executionFailure = error;
3077
+ break;
3078
+ }
3079
+ if (analysisFailure === void 0) {
2129
3080
  summary = {
2130
3081
  analyst_id: analyst.id,
2131
3082
  status: "ok",
2132
3083
  findings_count: findings.length,
2133
- latency_ms: latency,
2134
- usage
3084
+ latency_ms: Date.now() - t0,
3085
+ usage,
3086
+ ...exact ? { allocated_budget_usd: effectiveBudget ?? null } : {}
2135
3087
  };
2136
- summaries.push(summary);
2137
3088
  log(`[analyst] ok ${analyst.id}`, {
2138
- runId,
3089
+ runId: plan.runId,
2139
3090
  findings: findings.length,
2140
- latency_ms: latency,
2141
- cost_usd: cost,
3091
+ latency_ms: summary.latency_ms,
3092
+ cost_usd: knownCostUsd(usage),
2142
3093
  cost_kind: usage.cost.kind,
2143
3094
  input_tokens: usage.tokens?.input ?? null,
2144
3095
  output_tokens: usage.tokens?.output ?? null
2145
3096
  });
2146
- if (effectiveBudget !== void 0 && usage.cost.kind === "uncaptured") log(`[analyst] WARN ${analyst.id} USD cost uncaptured; budget not reconciled`, {
2147
- runId,
2148
- budget_usd: effectiveBudget,
2149
- cost_captured: false
2150
- });
2151
- } catch (err) {
2152
- const latency = Date.now() - t0;
2153
- const e = err instanceof Error ? err : new Error(String(err));
2154
- const hookFindings = runSignal?.aborted ? [] : await hooks.onError?.({
2155
- analyst,
2156
- error: e,
2157
- runId
2158
- }) ?? [];
2159
- if (hookFindings.length) allFindings.push(...hookFindings);
2160
- const usage = resolveUsage(analyst, usageReceipts);
2161
- const cost = knownCostUsd(usage);
2162
- totalCost += cost;
2163
- if (typeof remainingUsd === "number") remainingUsd = Math.max(0, remainingUsd - budgetDebit(usage, effectiveBudget));
2164
- const summary = {
3097
+ } else {
3098
+ const errorClass = analysisFailure.constructor.name || "Error";
3099
+ const errorMessage = exact && analysisFailure.message.length === 0 ? "Analyst failed without an error message" : analysisFailure.message;
3100
+ summary = {
2165
3101
  analyst_id: analyst.id,
2166
3102
  status: "failed",
2167
- findings_count: hookFindings.length,
2168
- latency_ms: latency,
3103
+ findings_count: findings.length,
3104
+ latency_ms: Date.now() - t0,
2169
3105
  usage,
3106
+ ...exact ? { allocated_budget_usd: effectiveBudget ?? null } : {},
2170
3107
  error: {
2171
- class: e.constructor.name,
2172
- message: e.message
3108
+ class: errorClass,
3109
+ message: errorMessage
2173
3110
  }
2174
3111
  };
2175
- summaries.push(summary);
2176
3112
  log(`[analyst] FAIL ${analyst.id}`, {
2177
- runId,
2178
- error_class: e.constructor.name,
2179
- error: e.message,
2180
- cost_usd: cost,
3113
+ runId: plan.runId,
3114
+ error_class: errorClass,
3115
+ error: errorMessage,
3116
+ cost_usd: knownCostUsd(usage),
2181
3117
  cost_kind: usage.cost.kind
2182
3118
  });
2183
- if (effectiveBudget !== void 0 && usage.cost.kind === "uncaptured") log(`[analyst] WARN ${analyst.id} — USD cost uncaptured; budget not reconciled`, {
2184
- runId,
2185
- budget_usd: effectiveBudget,
2186
- cost_captured: false
2187
- });
2188
- await waitForHook(hooks.onAfterAnalyze ? () => hooks.onAfterAnalyze?.({
2189
- analyst,
2190
- summary,
2191
- findings: hookFindings,
2192
- runId
2193
- }) : void 0, runSignal);
2194
- yield {
2195
- type: "analyst-completed",
2196
- summary,
2197
- findings: hookFindings
2198
- };
2199
- continue;
2200
3119
  }
2201
- await waitForHook(hooks.onAfterAnalyze ? () => hooks.onAfterAnalyze?.({
3120
+ logUncapturedBudgetWarning({
2202
3121
  analyst,
3122
+ runId: plan.runId,
3123
+ budgetUsd: effectiveBudget,
3124
+ usage,
3125
+ log
3126
+ });
3127
+ const execution = {
2203
3128
  summary,
2204
3129
  findings,
2205
- runId
2206
- }) : void 0, runSignal);
2207
- yield {
3130
+ budgetDebitUsd: budgetDebit(summary.usage, effectiveBudget)
3131
+ };
3132
+ if (exact) try {
3133
+ executionCost([...executions, execution], true);
3134
+ } catch (error) {
3135
+ executionFailure = error;
3136
+ break;
3137
+ }
3138
+ executions.push(execution);
3139
+ if (plan.budget.kind === "dynamic" && remainingUsd !== void 0) remainingUsd = Math.max(0, remainingUsd - execution.budgetDebitUsd);
3140
+ if (plan.chainFindings) upstreamFindings.push(...findings);
3141
+ if (lifecycleFailure !== void 0) {
3142
+ if (!exact) throw lifecycleFailure;
3143
+ executionFailure = lifecycleFailure;
3144
+ break;
3145
+ }
3146
+ const hookValues = snapshotAfterHookValues(summary, findings, exact);
3147
+ try {
3148
+ await waitForHook(plan.hooks.onAfterAnalyze ? () => plan.hooks.onAfterAnalyze?.({
3149
+ analyst,
3150
+ summary: hookValues.summary,
3151
+ findings: hookValues.findings,
3152
+ runId: plan.runId
3153
+ }) : void 0, executionSignal);
3154
+ } catch (error) {
3155
+ if (!exact) throw error;
3156
+ executionFailure = error;
3157
+ break;
3158
+ }
3159
+ yield snapshotExecutionEvent({
2208
3160
  type: "analyst-completed",
2209
3161
  summary,
2210
3162
  findings
2211
- };
3163
+ }, exact);
3164
+ if (exact && runSignal?.aborted) {
3165
+ executionFailure = abortReason(runSignal);
3166
+ break;
3167
+ }
2212
3168
  }
2213
- const result = {
2214
- run_id: runId,
3169
+ const summaries = executions.map(({ summary }) => summary);
3170
+ const findings = executions.flatMap((execution) => execution.findings);
3171
+ const cost = executionCost(executions, exact);
3172
+ const baseResult = {
3173
+ run_id: plan.runId,
2215
3174
  correlation_id: correlationId,
2216
3175
  started_at: startedAt,
2217
3176
  ended_at: (/* @__PURE__ */ new Date()).toISOString(),
2218
- findings: allFindings,
3177
+ findings,
2219
3178
  per_analyst: summaries,
2220
- total_cost_usd: totalCost,
2221
- total_cost_provenance: aggregateCostProvenance(summaries.map((summary) => summary.usage?.cost ?? {
2222
- kind: "uncaptured",
2223
- usd: null
2224
- }))
2225
- };
2226
- await waitForHook(hooks.onComplete ? () => hooks.onComplete?.({ result }) : void 0, runSignal);
2227
- yield {
2228
- type: "run-completed",
2229
- result
3179
+ total_cost_usd: cost.known,
3180
+ total_cost_provenance: cost.provenance
2230
3181
  };
3182
+ if (plan.executionSnapshot === void 0) {
3183
+ await waitForHook(plan.hooks.onComplete ? () => plan.hooks.onComplete?.({ result: baseResult }) : void 0, runSignal);
3184
+ yield {
3185
+ type: "run-completed",
3186
+ result: baseResult
3187
+ };
3188
+ return;
3189
+ }
3190
+ let completeResult;
3191
+ if (executionFailure === void 0) try {
3192
+ completeResult = snapshotExactAnalystRunReceipt({
3193
+ ...baseResult,
3194
+ execution_plan: plan.executionSnapshot,
3195
+ completion: { status: "complete" }
3196
+ }, "AnalystRegistry.runExact result");
3197
+ await waitForHook(plan.hooks.onComplete ? () => plan.hooks.onComplete?.({ result: completeResult }) : void 0, runSignal);
3198
+ } catch (error) {
3199
+ executionFailure = error;
3200
+ }
3201
+ if (runSignal?.aborted) executionFailure ??= abortReason(runSignal);
3202
+ if (executionFailure === void 0 && completeResult) {
3203
+ yield snapshotExecutionEvent({
3204
+ type: "run-completed",
3205
+ result: completeResult
3206
+ }, true);
3207
+ return;
3208
+ }
3209
+ const cause = executionFailure instanceof Error ? executionFailure : new Error(String(executionFailure));
3210
+ const errorClass = cause.constructor.name || "Error";
3211
+ const errorMessage = cause.message.trim().length === 0 ? "Exact analyst run failed without a message" : cause.message;
3212
+ throw new ExactAnalystRunExecutionError(`exact analyst run failed after starting: ${errorMessage}; partial result is attached`, {
3213
+ ...baseResult,
3214
+ execution_plan: plan.executionSnapshot,
3215
+ completion: {
3216
+ status: "failed",
3217
+ error: {
3218
+ class: errorClass,
3219
+ message: errorMessage
3220
+ }
3221
+ }
3222
+ }, { cause });
2231
3223
  }
2232
3224
  selectAnalysts(opts) {
2233
3225
  let candidates = Array.from(this.analysts.values());
@@ -2241,34 +3233,377 @@ var AnalystRegistry = class {
2241
3233
  }
2242
3234
  return candidates;
2243
3235
  }
3236
+ selectExactAnalysts(ids) {
3237
+ return ids.map((id) => {
3238
+ const analyst = this.analysts.get(id);
3239
+ if (!analyst) throw new Error(`ExactRegistryRunOpts.analystIds names unknown analyst "${id}"`);
3240
+ return {
3241
+ registeredId: id,
3242
+ analyst
3243
+ };
3244
+ });
3245
+ }
2244
3246
  routeInput(analyst, inputs) {
2245
3247
  switch (analyst.inputKind) {
2246
- case "trace-store": return inputs.traceStore ? {
2247
- kind: "present",
2248
- value: inputs.traceStore
2249
- } : { kind: "missing" };
2250
- case "artifact-dir": return inputs.artifactDir ? {
2251
- kind: "present",
2252
- value: inputs.artifactDir
2253
- } : { kind: "missing" };
2254
- case "run-record": return inputs.runRecord ? {
2255
- kind: "present",
2256
- value: inputs.runRecord
2257
- } : { kind: "missing" };
2258
- case "judge-input": return inputs.judgeInput ? {
2259
- kind: "present",
2260
- value: inputs.judgeInput
2261
- } : { kind: "missing" };
3248
+ case "trace-store": {
3249
+ const value = inputs.traceStore;
3250
+ return value ? {
3251
+ kind: "present",
3252
+ value
3253
+ } : { kind: "missing" };
3254
+ }
3255
+ case "artifact-dir": {
3256
+ const value = inputs.artifactDir;
3257
+ return value ? {
3258
+ kind: "present",
3259
+ value
3260
+ } : { kind: "missing" };
3261
+ }
3262
+ case "run-record": {
3263
+ const value = inputs.runRecord;
3264
+ return value ? {
3265
+ kind: "present",
3266
+ value
3267
+ } : { kind: "missing" };
3268
+ }
3269
+ case "judge-input": {
3270
+ const value = inputs.judgeInput;
3271
+ return value ? {
3272
+ kind: "present",
3273
+ value
3274
+ } : { kind: "missing" };
3275
+ }
2262
3276
  case "custom": {
2263
- const v = inputs.custom?.[analyst.id];
2264
- return v !== void 0 ? {
3277
+ const value = inputs.custom?.[analyst.id];
3278
+ return value !== void 0 ? {
2265
3279
  kind: "present",
2266
- value: v
3280
+ value
2267
3281
  } : { kind: "missing" };
2268
3282
  }
2269
3283
  }
2270
3284
  }
2271
3285
  };
3286
+ const exactRunFields = [
3287
+ "analystIds",
3288
+ "budget",
3289
+ "totalTimeoutMs",
3290
+ "signal",
3291
+ "costLedger",
3292
+ "costLedgerIdentity",
3293
+ "costPhase",
3294
+ "tags",
3295
+ "priorFindings",
3296
+ "chainFindings",
3297
+ "missingInputMode",
3298
+ "applyRegistryHooks",
3299
+ "useRegistryChat"
3300
+ ];
3301
+ const exactNonEmptyString = z.string().min(1);
3302
+ const exactFiniteNonnegative = z.number().finite().nonnegative();
3303
+ const exactBudgetSchema = z.discriminatedUnion("kind", [z.strictObject({
3304
+ kind: z.literal("equal"),
3305
+ totalUsd: exactFiniteNonnegative
3306
+ }), z.strictObject({
3307
+ kind: z.literal("weighted"),
3308
+ totalUsd: exactFiniteNonnegative,
3309
+ weights: z.record(exactNonEmptyString, exactFiniteNonnegative)
3310
+ })]);
3311
+ const exactRunDataSchema = z.strictObject({
3312
+ analystIds: z.array(exactNonEmptyString).min(1),
3313
+ budget: exactBudgetSchema.nullable(),
3314
+ totalTimeoutMs: z.number().int().positive().max(2147483647).nullable(),
3315
+ costLedgerIdentity: z.unknown().nullable(),
3316
+ costPhase: exactNonEmptyString.nullable(),
3317
+ tags: z.record(z.string(), z.string()).nullable(),
3318
+ chainFindings: z.boolean(),
3319
+ missingInputMode: z.enum(["skip", "abort"]),
3320
+ applyRegistryHooks: z.boolean(),
3321
+ useRegistryChat: z.boolean()
3322
+ }).superRefine((policy, context) => {
3323
+ const issue = (path, message) => context.addIssue({
3324
+ code: "custom",
3325
+ path,
3326
+ message
3327
+ });
3328
+ if (new Set(policy.analystIds).size !== policy.analystIds.length) issue(["analystIds"], "must not contain duplicates");
3329
+ if (policy.budget?.kind === "weighted" && Object.values(policy.budget.weights).every((weight) => weight === 0)) issue(["budget", "weights"], "must allocate positive weight to at least one analyst");
3330
+ if (policy.budget?.kind === "weighted") {
3331
+ const selected = [...policy.analystIds].sort();
3332
+ const weighted = Object.keys(policy.budget.weights).sort();
3333
+ if (selected.length !== weighted.length || selected.some((id, index) => id !== weighted[index])) issue(["budget", "weights"], "must name every selected analyst and no others");
3334
+ }
3335
+ });
3336
+ /** Validate the canonical exact-run policy before any analyst can start. */
3337
+ function assertExactRegistryRunOpts(value) {
3338
+ snapshotExactRegistryRunOpts(value);
3339
+ }
3340
+ function snapshotExactRunId(value) {
3341
+ if (typeof value !== "string" || value.length === 0) throw new TypeError("AnalystRegistry.runExact: runId must be a non-empty string");
3342
+ return canonicalJsonSnapshot(value, "AnalystRegistry.runExact runId");
3343
+ }
3344
+ function snapshotAnalystRunInputChannels(inputs) {
3345
+ if (!inputs || typeof inputs !== "object" || Array.isArray(inputs)) throw new TypeError("AnalystRegistry.runExact: inputs must be an object");
3346
+ const traceStore = inputs.traceStore;
3347
+ const artifactDir = inputs.artifactDir;
3348
+ const runRecord = inputs.runRecord;
3349
+ const judgeInput = inputs.judgeInput;
3350
+ const custom = inputs.custom;
3351
+ return Object.freeze({
3352
+ traceStore,
3353
+ artifactDir,
3354
+ runRecord,
3355
+ judgeInput,
3356
+ custom
3357
+ });
3358
+ }
3359
+ /**
3360
+ * Read the untrusted caller object once, then validate and execute only this frozen snapshot.
3361
+ * Functions and resource handles retain identity; all data fields are copied canonically.
3362
+ */
3363
+ function snapshotExactRegistryRunOpts(value) {
3364
+ const captured = readOwnFields(value, exactRunFields, "ExactRegistryRunOpts");
3365
+ const missing = exactRunFields.find((field) => !Object.hasOwn(captured, field));
3366
+ if (missing) throw new TypeError(`ExactRegistryRunOpts.${missing} must be supplied explicitly`);
3367
+ const { signal, costLedger, priorFindings, ...rawData } = captured;
3368
+ const data = canonicalJsonSnapshot(rawData, "ExactRegistryRunOpts");
3369
+ const parsed = exactRunDataSchema.safeParse(data);
3370
+ if (!parsed.success) {
3371
+ const issue = parsed.error.issues[0];
3372
+ if (issue?.code === "unrecognized_keys" && issue.path.join(".") === "budget") {
3373
+ const required = isPlainRecord(data.budget) && data.budget.kind === "weighted" ? "kind, totalUsd, weights" : "kind, totalUsd";
3374
+ throw new TypeError(`ExactRegistryRunOpts.budget must contain exactly ${required}`);
3375
+ }
3376
+ const path = issue?.path.length ? `.${issue.path.join(".")}` : "";
3377
+ throw new TypeError(`ExactRegistryRunOpts${path}: ${issue?.message ?? "is invalid"}`);
3378
+ }
3379
+ if (signal !== null && (!signal || typeof signal !== "object" || typeof signal.addEventListener !== "function")) throw new TypeError("ExactRegistryRunOpts.signal must be an AbortSignal or null");
3380
+ if (costLedger !== null && (!costLedger || typeof costLedger !== "object")) throw new TypeError("ExactRegistryRunOpts.costLedger must be a CostLedgerHandle or null");
3381
+ if (costLedger === null && parsed.data.costLedgerIdentity !== null) throw new TypeError("ExactRegistryRunOpts.costLedgerIdentity must be null without costLedger");
3382
+ if (costLedger !== null && parsed.data.costLedgerIdentity === null) throw new TypeError("ExactRegistryRunOpts.costLedgerIdentity is required with costLedger");
3383
+ if (costLedger === null && parsed.data.costPhase !== null) throw new TypeError("ExactRegistryRunOpts.costPhase requires a non-null costLedger");
3384
+ return Object.freeze({
3385
+ ...deepFreezeCanonicalJson(parsed.data),
3386
+ signal,
3387
+ costLedger,
3388
+ priorFindings: snapshotExactPriorFindings(priorFindings)
3389
+ });
3390
+ }
3391
+ function snapshotExactPriorFindings(value) {
3392
+ if (value === null) return null;
3393
+ if (Array.isArray(value)) return snapshotAnalystFindings(value, "ExactRegistryRunOpts.priorFindings");
3394
+ if (!isPlainRecord(value)) throw new TypeError("ExactRegistryRunOpts.priorFindings must be an array, a findings record, or null");
3395
+ const result = {};
3396
+ for (const [key, findings] of Object.entries(value)) {
3397
+ if (!Array.isArray(findings)) throw new TypeError(`ExactRegistryRunOpts.priorFindings.${key} must be an array`);
3398
+ result[key] = snapshotAnalystFindings(findings, `ExactRegistryRunOpts.priorFindings.${key}`);
3399
+ }
3400
+ return deepFreezeCanonicalJson(result);
3401
+ }
3402
+ function normalizeExactAnalysts(selections) {
3403
+ return selections.map(({ registeredId, analyst }) => {
3404
+ const exactAnalyst = analyst;
3405
+ const id = analyst.id;
3406
+ const description = analyst.description;
3407
+ const inputKind = analyst.inputKind;
3408
+ const rawCostValue = analyst.cost;
3409
+ const requiresValue = analyst.requires;
3410
+ const version = analyst.version;
3411
+ const executionConfigValue = exactAnalyst.executionConfig;
3412
+ const analyzeValue = analyst.analyze;
3413
+ if (id !== registeredId) throw new TypeError(`AnalystRegistry.runExact: registered analyst "${registeredId}" changed id to "${id}"`);
3414
+ if (executionConfigValue === void 0) throw new TypeError(`AnalystRegistry.runExact: analyst "${id}" must declare executionConfig`);
3415
+ const executionConfig = canonicalJsonSnapshot(executionConfigValue, `AnalystRegistry.runExact analyst "${id}" executionConfig`);
3416
+ if (!isPlainRecord(executionConfig)) throw new TypeError(`AnalystRegistry.runExact analyst "${id}" executionConfig must be an object`);
3417
+ const rawCost = canonicalJsonSnapshot(rawCostValue, `AnalystRegistry.runExact analyst "${id}" cost`);
3418
+ const cost = rawCost.kind === "llm" ? Object.freeze({
3419
+ ...rawCost,
3420
+ settlement_timeout_ms: validateUsageSettlementTimeout(rawCost.settlement_timeout_ms)
3421
+ }) : rawCost;
3422
+ const requires = requiresValue === void 0 ? void 0 : canonicalJsonSnapshot(requiresValue, `AnalystRegistry.runExact analyst "${id}" requirements`);
3423
+ const analyze = analyzeValue.bind(analyst);
3424
+ return Object.freeze({
3425
+ id,
3426
+ description,
3427
+ inputKind,
3428
+ cost,
3429
+ ...requires === void 0 ? {} : { requires },
3430
+ version,
3431
+ executionConfig,
3432
+ analyze
3433
+ });
3434
+ });
3435
+ }
3436
+ function hasRegistryHooks(hooks) {
3437
+ return Boolean(hooks && (hooks.onBeforeAnalyze || hooks.onAfterAnalyze || hooks.onError || hooks.onComplete));
3438
+ }
3439
+ function snapshotHooks(hooks) {
3440
+ const onBeforeAnalyze = hooks.onBeforeAnalyze;
3441
+ const onAfterAnalyze = hooks.onAfterAnalyze;
3442
+ const onError = hooks.onError;
3443
+ const onComplete = hooks.onComplete;
3444
+ return Object.freeze({
3445
+ ...onBeforeAnalyze === void 0 ? {} : { onBeforeAnalyze: onBeforeAnalyze.bind(hooks) },
3446
+ ...onAfterAnalyze === void 0 ? {} : { onAfterAnalyze: onAfterAnalyze.bind(hooks) },
3447
+ ...onError === void 0 ? {} : { onError: onError.bind(hooks) },
3448
+ ...onComplete === void 0 ? {} : { onComplete: onComplete.bind(hooks) }
3449
+ });
3450
+ }
3451
+ function snapshotChat(chat) {
3452
+ const transport = chat.transport;
3453
+ const defaultModel = chat.defaultModel;
3454
+ const maximumAttempts = chat.maximumAttempts;
3455
+ const call = chat.chat;
3456
+ return Object.freeze({
3457
+ transport,
3458
+ ...defaultModel === void 0 ? {} : { defaultModel },
3459
+ ...maximumAttempts === void 0 ? {} : { maximumAttempts },
3460
+ chat: call.bind(chat)
3461
+ });
3462
+ }
3463
+ function requireExactComponentIdentity(value, label) {
3464
+ if (value === void 0) throw new TypeError(`AnalystRegistry.runExact: ${label} requires a versioned identity`);
3465
+ return snapshotExactExecutionComponentIdentity(value, `AnalystRegistry.runExact ${label} identity`);
3466
+ }
3467
+ function exactExecutionSnapshot(analysts, opts, allocations, costLedger, hooks, chat) {
3468
+ const priorFindings = exactPriorFindingsSnapshot(opts.priorFindings);
3469
+ const budget = opts.budget === null ? { kind: "none" } : opts.budget.kind === "equal" ? {
3470
+ kind: "equal",
3471
+ total_usd: opts.budget.totalUsd,
3472
+ allocations_usd: { ...allocations }
3473
+ } : {
3474
+ kind: "weighted",
3475
+ total_usd: opts.budget.totalUsd,
3476
+ weights: { ...opts.budget.weights },
3477
+ allocations_usd: { ...allocations }
3478
+ };
3479
+ const material = {
3480
+ schema_version: "1.0.0",
3481
+ analysts: analysts.map((analyst) => ({
3482
+ id: analyst.id,
3483
+ version: analyst.version,
3484
+ input_kind: analyst.inputKind,
3485
+ cost: analyst.cost,
3486
+ requirements: analyst.requires ?? null,
3487
+ execution_config_digest: hashCanonical(analyst.executionConfig)
3488
+ })),
3489
+ policy: {
3490
+ budget,
3491
+ total_timeout_ms: opts.totalTimeoutMs,
3492
+ signal_provided: opts.signal !== null,
3493
+ cost_ledger: costLedger,
3494
+ cost_phase: opts.costPhase,
3495
+ tags: opts.tags === null ? null : { ...opts.tags },
3496
+ prior_findings: priorFindings,
3497
+ chain_findings: opts.chainFindings,
3498
+ missing_input_mode: opts.missingInputMode,
3499
+ registry_hooks: hooks,
3500
+ registry_chat: chat
3501
+ }
3502
+ };
3503
+ return snapshotExactExecutionPlan({
3504
+ ...material,
3505
+ digest: hashCanonical(material)
3506
+ }, "AnalystRegistry.runExact execution plan");
3507
+ }
3508
+ function exactPriorFindingsSnapshot(findings) {
3509
+ if (findings === null) return { kind: "none" };
3510
+ if (Array.isArray(findings)) return {
3511
+ kind: "ordered",
3512
+ count: findings.length,
3513
+ digest: hashCanonical(findings)
3514
+ };
3515
+ const record = findings;
3516
+ const keys = Object.keys(record).sort();
3517
+ return {
3518
+ kind: "by_analyst",
3519
+ keys,
3520
+ count: keys.reduce((sum, key) => sum + (record[key]?.length ?? 0), 0),
3521
+ digest: hashCanonical(record)
3522
+ };
3523
+ }
3524
+ function canonicalJsonSnapshot(value, label) {
3525
+ let snapshot;
3526
+ try {
3527
+ snapshot = JSON.parse(canonicalString(value));
3528
+ } catch (cause) {
3529
+ throw new TypeError(`${label} must be canonical JSON`, { cause });
3530
+ }
3531
+ return deepFreezeCanonicalJson(snapshot);
3532
+ }
3533
+ function snapshotUsageReceiptOnce(receipt, context) {
3534
+ const data = readOwnFields(receipt, [
3535
+ "calls",
3536
+ "tokens",
3537
+ "cost",
3538
+ "knownCostUsd"
3539
+ ], context);
3540
+ data.tokens = data.tokens === null ? null : readOwnFields(data.tokens, [
3541
+ "input",
3542
+ "output",
3543
+ "reasoning",
3544
+ "cached",
3545
+ "cacheWrite"
3546
+ ], `${context} tokens`);
3547
+ data.cost = readOwnFields(data.cost, ["kind", "usd"], `${context} cost`);
3548
+ const snapshot = canonicalJsonSnapshot(data, context);
3549
+ assertValidAnalystUsageReceipt(snapshot, context);
3550
+ return snapshot;
3551
+ }
3552
+ function readOwnFields(value, fields, context) {
3553
+ if (!isPlainRecord(value)) throw new TypeError(`${context} must be a plain object`);
3554
+ const unexpected = Object.keys(value).filter((key) => !fields.includes(key));
3555
+ if (unexpected.length > 0) throw new TypeError(`${context} contains unknown fields: ${unexpected.sort().join(", ")}`);
3556
+ return Object.fromEntries(fields.flatMap((field) => Object.hasOwn(value, field) ? [[field, value[field]]] : []));
3557
+ }
3558
+ function isPlainRecord(value) {
3559
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
3560
+ const prototype = Object.getPrototypeOf(value);
3561
+ return prototype === Object.prototype || prototype === null;
3562
+ }
3563
+ function exactFixedBudgets(exact, runnable, selected) {
3564
+ if (exact === null) return {};
3565
+ const allocations = Object.fromEntries(selected.map((analyst) => [analyst.id, null]));
3566
+ if (runnable.length === 0) return deepFreezeCanonicalJson(allocations);
3567
+ if (exact.kind === "equal") {
3568
+ const each = exact.totalUsd / runnable.length;
3569
+ for (const analyst of runnable) allocations[analyst.id] = each;
3570
+ return deepFreezeCanonicalJson(allocations);
3571
+ }
3572
+ const totalWeight = runnable.reduce((sum, analyst) => sum + exact.weights[analyst.id], 0);
3573
+ if (totalWeight === 0) throw new Error("ExactRegistryRunOpts weighted budget must allocate positive weight to a runnable analyst");
3574
+ for (const analyst of runnable) allocations[analyst.id] = exact.totalUsd * exact.weights[analyst.id] / totalWeight;
3575
+ return deepFreezeCanonicalJson(allocations);
3576
+ }
3577
+ function snapshotExecutionFindings(findings, exact, context) {
3578
+ return exact ? deepFreezeCanonicalJson(snapshotAnalystFindings(findings, context)) : findings;
3579
+ }
3580
+ function snapshotAfterHookValues(summary, findings, exact) {
3581
+ if (!exact) return {
3582
+ summary,
3583
+ findings
3584
+ };
3585
+ return {
3586
+ summary: canonicalJsonSnapshot(summary, "AnalystRegistry.runExact onAfterAnalyze summary"),
3587
+ findings: deepFreezeCanonicalJson(snapshotAnalystFindings(findings, "AnalystRegistry.runExact onAfterAnalyze findings"))
3588
+ };
3589
+ }
3590
+ function exactPlannedAllocation(plan, analystId) {
3591
+ const budget = plan.policy.budget;
3592
+ if (budget.kind === "none") return void 0;
3593
+ const allocated = budget.allocations_usd[analystId];
3594
+ return allocated === null ? void 0 : allocated;
3595
+ }
3596
+ function snapshotExecutionEvent(event, exact) {
3597
+ return exact ? canonicalJsonSnapshot(event, "AnalystRegistry.runExact event") : event;
3598
+ }
3599
+ function logUncapturedBudgetWarning(args) {
3600
+ if (args.budgetUsd === void 0 || args.usage.cost.kind !== "uncaptured") return;
3601
+ args.log(`[analyst] WARN ${args.analyst.id} — USD cost uncaptured; budget not reconciled`, {
3602
+ runId: args.runId,
3603
+ budget_usd: args.budgetUsd,
3604
+ cost_captured: false
3605
+ });
3606
+ }
2272
3607
  function validateTimeout(timeoutMs) {
2273
3608
  if (timeoutMs === void 0) return void 0;
2274
3609
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) throw new TypeError("RegistryRunOpts.timeoutMs must be a positive safe integer no greater than 2147483647");
@@ -2398,8 +3733,8 @@ function zeroUsage() {
2398
3733
  }
2399
3734
  };
2400
3735
  }
2401
- function resolveUsage(analyst, receipts) {
2402
- if (receipts.length > 0) return mergeUsageReceipts(receipts);
3736
+ function resolveUsage(analyst, receipts, exact = false) {
3737
+ if (receipts.length > 0) return mergeUsageReceipts(receipts, exact);
2403
3738
  if (analyst.cost.kind === "deterministic") return zeroUsage();
2404
3739
  return {
2405
3740
  calls: null,
@@ -2410,24 +3745,21 @@ function resolveUsage(analyst, receipts) {
2410
3745
  }
2411
3746
  };
2412
3747
  }
2413
- function mergeUsageReceipts(receipts) {
2414
- const calls = receipts.every((receipt) => receipt.calls !== null) ? receipts.reduce((sum, receipt) => sum + (receipt.calls ?? 0), 0) : null;
2415
- const tokens = receipts.every((receipt) => receipt.tokens !== null) ? receipts.reduce((sum, receipt) => ({
2416
- input: sum.input + (receipt.tokens?.input ?? 0),
2417
- output: sum.output + (receipt.tokens?.output ?? 0),
2418
- ...sum.reasoning !== void 0 || receipt.tokens?.reasoning !== void 0 ? { reasoning: (sum.reasoning ?? 0) + (receipt.tokens?.reasoning ?? 0) } : {},
2419
- ...sum.cached !== void 0 || receipt.tokens?.cached !== void 0 ? { cached: (sum.cached ?? 0) + (receipt.tokens?.cached ?? 0) } : {},
2420
- ...sum.cacheWrite !== void 0 || receipt.tokens?.cacheWrite !== void 0 ? { cacheWrite: (sum.cacheWrite ?? 0) + (receipt.tokens?.cacheWrite ?? 0) } : {}
2421
- }), {
2422
- input: 0,
2423
- output: 0
2424
- }) : null;
2425
- const cost = aggregateCostProvenance(receipts.map((receipt) => receipt.cost));
3748
+ function mergeUsageReceipts(receipts, exact = false) {
3749
+ const calls = receipts.every((receipt) => receipt.calls !== null) ? usageSum(receipts.map((receipt) => receipt.calls ?? 0), exact, "calls", true) : null;
3750
+ const tokens = receipts.every((receipt) => receipt.tokens !== null) ? Object.fromEntries([
3751
+ "input",
3752
+ "output",
3753
+ "reasoning",
3754
+ "cached",
3755
+ "cacheWrite"
3756
+ ].flatMap((field) => field === "input" || field === "output" || receipts.some((receipt) => receipt.tokens?.[field] !== void 0) ? [[field, usageSum(receipts.map((receipt) => receipt.tokens?.[field] ?? 0), exact, `tokens.${field}`, true)]] : [])) : null;
3757
+ const cost = aggregateCostProvenance(receipts.map((receipt) => receipt.cost), exact);
2426
3758
  return {
2427
3759
  calls,
2428
3760
  tokens,
2429
3761
  cost,
2430
- ...cost.kind === "uncaptured" ? { knownCostUsd: receipts.reduce((sum, receipt) => sum + knownCostUsd(receipt), 0) } : {}
3762
+ ...cost.kind === "uncaptured" ? { knownCostUsd: usageSum(receipts.map(knownCostUsd), exact, "known cost") } : {}
2431
3763
  };
2432
3764
  }
2433
3765
  function knownCostUsd(receipt) {
@@ -2437,12 +3769,12 @@ function budgetDebit(receipt, allocatedUsd) {
2437
3769
  const known = knownCostUsd(receipt);
2438
3770
  return receipt.cost.kind === "uncaptured" && allocatedUsd !== void 0 ? Math.max(known, allocatedUsd) : known;
2439
3771
  }
2440
- function aggregateCostProvenance(costs) {
3772
+ function aggregateCostProvenance(costs, exact = false) {
2441
3773
  if (costs.some((cost) => cost.kind === "uncaptured")) return {
2442
3774
  kind: "uncaptured",
2443
3775
  usd: null
2444
3776
  };
2445
- const usd = costs.reduce((sum, cost) => sum + (cost.usd ?? 0), 0);
3777
+ const usd = usageSum(costs.map((cost) => cost.usd ?? 0), exact, "captured cost");
2446
3778
  return costs.some((cost) => cost.kind === "estimated") ? {
2447
3779
  kind: "estimated",
2448
3780
  usd
@@ -2451,24 +3783,17 @@ function aggregateCostProvenance(costs) {
2451
3783
  usd
2452
3784
  };
2453
3785
  }
2454
- function assertValidUsageReceipt(receipt) {
2455
- if (receipt.calls !== null && (!Number.isInteger(receipt.calls) || receipt.calls < 0)) throw new Error("AnalystContext.recordUsage: calls must be a non-negative integer or null");
2456
- if (receipt.tokens) {
2457
- assertNonNegativeFinite(receipt.tokens.input, "tokens.input");
2458
- assertNonNegativeFinite(receipt.tokens.output, "tokens.output");
2459
- if (receipt.tokens.reasoning !== void 0) {
2460
- assertNonNegativeFinite(receipt.tokens.reasoning, "tokens.reasoning");
2461
- if (receipt.tokens.reasoning > receipt.tokens.output) throw new Error("AnalystContext.recordUsage: tokens.reasoning must not exceed tokens.output");
2462
- }
2463
- if (receipt.tokens.cached !== void 0) assertNonNegativeFinite(receipt.tokens.cached, "tokens.cached");
2464
- if (receipt.tokens.cacheWrite !== void 0) assertNonNegativeFinite(receipt.tokens.cacheWrite, "tokens.cacheWrite");
2465
- }
2466
- if (receipt.cost.kind !== "uncaptured") assertNonNegativeFinite(receipt.cost.usd, "cost.usd");
2467
- else if (receipt.cost.usd !== null) throw new Error("AnalystContext.recordUsage: uncaptured cost.usd must be null");
2468
- if (receipt.knownCostUsd !== void 0) assertNonNegativeFinite(receipt.knownCostUsd, "knownCostUsd");
3786
+ function executionCost(executions, exact) {
3787
+ const usages = executions.map((execution) => execution.summary.usage);
3788
+ return {
3789
+ known: usageSum(usages.map(knownCostUsd), exact, "run known cost"),
3790
+ provenance: aggregateCostProvenance(usages.map((usage) => usage.cost), exact)
3791
+ };
2469
3792
  }
2470
- function assertNonNegativeFinite(value, field) {
2471
- if (!Number.isFinite(value) || value < 0) throw new Error(`AnalystContext.recordUsage: ${field} must be a non-negative finite number`);
3793
+ function usageSum(values, exact, field, integer = false) {
3794
+ const sum = values.reduce((total, value) => total + value, 0);
3795
+ if (exact && (integer ? !Number.isSafeInteger(sum) : !Number.isFinite(sum))) throw new RangeError(`exact analyst usage ${field} aggregate ${integer ? "exceeds a safe integer" : "is not finite"}`);
3796
+ return sum;
2472
3797
  }
2473
3798
  /**
2474
3799
  * Resolve the `priorFindings` slice an analyst sees.
@@ -2497,17 +3822,18 @@ function selectPriorFindings(source, analystId) {
2497
3822
  //#region src/analyst/default-registry.ts
2498
3823
  function buildDefaultAnalystRegistry(opts = {}) {
2499
3824
  const registry = new AnalystRegistry(opts.registry);
2500
- if (opts.includeBehavioral !== false) registry.register(behavioralAnalyst());
3825
+ if (opts.includeBehavioral !== false) registry.register(behavioralAnalyst(opts.behavioral));
2501
3826
  if (opts.ai) {
2502
3827
  const kinds = opts.kinds ?? DEFAULT_TRACE_ANALYST_KINDS;
2503
3828
  for (const spec of kinds) registry.register(createTraceAnalystKind(spec, {
2504
3829
  ai: opts.ai,
2505
- model: opts.model
3830
+ model: opts.model,
3831
+ aiIdentity: opts.aiIdentity
2506
3832
  }));
2507
3833
  }
2508
3834
  return registry;
2509
3835
  }
2510
3836
  //#endregion
2511
- export { parseFindingSubject as A, stripCodeFences as C, FindingSubjectStringSchema as D, FINDING_SUBJECT_SYNTAX as E, createChatClient as F, createAnalystAi as I, behavioralAnalyst as M, deriveEfficiencyFindings as N, KIND_EXPECTED_SUBJECTS as O, computeTraceMetrics as P, coerceToFindingRows as S, FINDING_SUBJECT_KINDS as T, RawAnalystEvidenceSchema as _, KNOWLEDGE_GAP_KIND_SPEC as a, parseRawFinding as b, buildTraceToolsForGroup as c, renderUpstreamFindings as d, settleUsageReceiptFromCostLedger as f, RAW_FINDING_SCHEMA_PROMPT as g, ANALYST_SEVERITIES as h, KNOWLEDGE_POISONING_KIND_SPEC as i, renderFindingSubject as j, findingSubjectGrammarPromptFor as k, createTraceAnalystKind as l, structureFindings as m, AnalystRegistry as n, IMPROVEMENT_KIND_SPEC as o, validateUsageSettlementTimeout as p, DEFAULT_TRACE_ANALYST_KINDS as r, FAILURE_MODE_KIND_SPEC as s, buildDefaultAnalystRegistry as t, renderPriorFindings as u, RawAnalystFindingSchema as v, FINDING_SUBJECT_GRAMMAR_PROMPT as w, coerceJson as x, evidenceRefsFromRawFinding as y };
3837
+ export { parseRawFinding as A, parseFindingSubject as B, renderUpstreamFindings as C, RawAnalystEvidenceSchema as D, RAW_FINDING_SCHEMA_PROMPT as E, FINDING_SUBJECT_KINDS as F, createChatClient as G, behavioralAnalyst as H, FINDING_SUBJECT_SYNTAX as I, createAnalystAi as K, FindingSubjectStringSchema as L, coerceToFindingRows as M, stripCodeFences as N, RawAnalystFindingSchema as O, FINDING_SUBJECT_GRAMMAR_PROMPT as P, KIND_EXPECTED_SUBJECTS as R, renderPriorFindings as S, ANALYST_SEVERITIES as T, deriveEfficiencyFindings as U, renderFindingSubject as V, computeTraceMetrics as W, buildTraceToolsForGroup as _, analystFindingDigest as a, emitControlIntegrityFindings as b, completedAnalystReviewQuality as c, validateAnalystReviewDecisions as d, DEFAULT_TRACE_ANALYST_KINDS as f, FAILURE_MODE_KIND_SPEC as g, IMPROVEMENT_KIND_SPEC as h, assertExactRegistryRunOpts as i, coerceJson as j, evidenceRefsFromRawFinding as k, readAnalystReview as l, KNOWLEDGE_GAP_KIND_SPEC as m, AnalystRegistry as n, analystRunDigest as o, KNOWLEDGE_POISONING_KIND_SPEC as p, ExactAnalystRunExecutionError as r, assertUniqueFindingIds as s, buildDefaultAnalystRegistry as t, snapshotAnalystRun as u, CONTROL_INTEGRITY_ANALYST as v, structureFindings as w, createTraceAnalystKind as x, ControlIntegrityAnalyst as y, findingSubjectGrammarPromptFor as z };
2512
3838
 
2513
- //# sourceMappingURL=default-registry-CHmdy2An.js.map
3839
+ //# sourceMappingURL=default-registry-lp5R0lve.js.map