@tangle-network/agent-eval 0.208.2 → 0.210.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 (237) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +1 -1
  3. package/dist/adapters/http.d.ts +2 -2
  4. package/dist/{agent-profile-CEhxR_I3.d.ts → agent-profile-BGyt2z8U.d.ts} +3 -3
  5. package/dist/{agent-profile-CEhxR_I3.d.ts.map → agent-profile-BGyt2z8U.d.ts.map} +1 -1
  6. package/dist/{agent-profile-cell-BHcB-mZX.d.ts → agent-profile-cell-BNp-Js9F.d.ts} +2 -3
  7. package/dist/agent-profile-cell-BNp-Js9F.d.ts.map +1 -0
  8. package/dist/{agent-profile-cell-DcFW4vOc.js → agent-profile-cell-BYc0bxsL.js} +2 -2
  9. package/dist/agent-profile-cell-BYc0bxsL.js.map +1 -0
  10. package/dist/analyst/index.d.ts +12 -12
  11. package/dist/analyst/index.js +5 -5
  12. package/dist/{analyze-runs-GZ0VSwui.js → analyze-runs-3xpby3p2.js} +6 -6
  13. package/dist/analyze-runs-3xpby3p2.js.map +1 -0
  14. package/dist/{analyze-runs-CTJyUplR.d.ts → analyze-runs-BlslTkOJ.d.ts} +4 -4
  15. package/dist/{analyze-runs-CTJyUplR.d.ts.map → analyze-runs-BlslTkOJ.d.ts.map} +1 -1
  16. package/dist/{benchmark-command-BC265T1r.js → benchmark-command-S8tCtkxh.js} +6 -6
  17. package/dist/{benchmark-command-BC265T1r.js.map → benchmark-command-S8tCtkxh.js.map} +1 -1
  18. package/dist/benchmarks/index.d.ts +5 -7
  19. package/dist/benchmarks/index.d.ts.map +1 -1
  20. package/dist/benchmarks/index.js +3 -3
  21. package/dist/builder-eval/index.js +1 -1
  22. package/dist/campaign/index.d.ts +6 -6
  23. package/dist/campaign/index.js +10 -10
  24. package/dist/{campaign-evidence-DW3Krxov.js → campaign-evidence-OcUciD-N.js} +4 -4
  25. package/dist/campaign-evidence-OcUciD-N.js.map +1 -0
  26. package/dist/{campaign-C5pjR-Qn.js → campaign-wvvJA1ps.js} +14 -14
  27. package/dist/{campaign-C5pjR-Qn.js.map → campaign-wvvJA1ps.js.map} +1 -1
  28. package/dist/claude-jsonl-dZir12m6.js +86 -0
  29. package/dist/claude-jsonl-dZir12m6.js.map +1 -0
  30. package/dist/cli.js +7 -7
  31. package/dist/contract/index.d.ts +10 -10
  32. package/dist/contract/index.js +92 -16
  33. package/dist/contract/index.js.map +1 -1
  34. package/dist/counterfactual-DtMWvn7D.js.map +1 -1
  35. package/dist/{default-registry-DZFWY2_E.d.ts → default-registry-Crk3QEy1.d.ts} +6 -6
  36. package/dist/{default-registry-DZFWY2_E.d.ts.map → default-registry-Crk3QEy1.d.ts.map} +1 -1
  37. package/dist/{default-registry-DX5R7_n9.js → default-registry-DF4Yp7HQ.js} +2 -2
  38. package/dist/{default-registry-DX5R7_n9.js.map → default-registry-DF4Yp7HQ.js.map} +1 -1
  39. package/dist/{define-agent-eval-C8zBSbsc.d.ts → define-agent-eval-B_zZY2AZ.d.ts} +7 -7
  40. package/dist/{define-agent-eval-C8zBSbsc.d.ts.map → define-agent-eval-B_zZY2AZ.d.ts.map} +1 -1
  41. package/dist/{define-agent-eval-BjKXcDQp.js → define-agent-eval-CoXXDCMu.js} +7 -7
  42. package/dist/{define-agent-eval-BjKXcDQp.js.map → define-agent-eval-CoXXDCMu.js.map} +1 -1
  43. package/dist/diagnosis/index.d.ts +2 -5
  44. package/dist/diagnosis/index.d.ts.map +1 -1
  45. package/dist/diagnosis/index.js +2 -26
  46. package/dist/diagnosis/index.js.map +1 -1
  47. package/dist/{dspy-rlm-engine-sHwdSxK3.js → dspy-rlm-engine-BpaKVKJA.js} +2 -2
  48. package/dist/{dspy-rlm-engine-sHwdSxK3.js.map → dspy-rlm-engine-BpaKVKJA.js.map} +1 -1
  49. package/dist/{engine-DProXJ7A.d.ts → engine-D-YZ32ZJ.d.ts} +5 -12
  50. package/dist/engine-D-YZ32ZJ.d.ts.map +1 -0
  51. package/dist/{eval-campaign-DU_p8qVt.js → eval-campaign-4byTtqrf.js} +4 -4
  52. package/dist/{eval-campaign-DU_p8qVt.js.map → eval-campaign-4byTtqrf.js.map} +1 -1
  53. package/dist/evaluation.d.ts +2 -2
  54. package/dist/{exact-types-Cq6xXNVR.d.ts → exact-types-DjhfZglz.d.ts} +2 -2
  55. package/dist/{exact-types-Cq6xXNVR.d.ts.map → exact-types-DjhfZglz.d.ts.map} +1 -1
  56. package/dist/experiment/index.d.ts +2 -2
  57. package/dist/experiment/index.js +8 -8
  58. package/dist/{feedback-trajectory-C6Z8PRoF.d.ts → feedback-trajectory-CX01700m.d.ts} +3 -3
  59. package/dist/{feedback-trajectory-C6Z8PRoF.d.ts.map → feedback-trajectory-CX01700m.d.ts.map} +1 -1
  60. package/dist/hosted/index.js +1 -1
  61. package/dist/{index-CbBfavbT.d.ts → index-D0_l25We.d.ts} +21 -42
  62. package/dist/{index-CbBfavbT.d.ts.map → index-D0_l25We.d.ts.map} +1 -1
  63. package/dist/{index-nwNoS582.d.ts → index-DnZdBxCL.d.ts} +14 -11
  64. package/dist/{index-nwNoS582.d.ts.map → index-DnZdBxCL.d.ts.map} +1 -1
  65. package/dist/{index-DkucR0KJ.d.ts → index-UxNTvn-w.d.ts} +6 -6
  66. package/dist/{index-DkucR0KJ.d.ts.map → index-UxNTvn-w.d.ts.map} +1 -1
  67. package/dist/{index-Bzto04CY.d.ts → index-xnhRdqik.d.ts} +9 -20
  68. package/dist/{index-Bzto04CY.d.ts.map → index-xnhRdqik.d.ts.map} +1 -1
  69. package/dist/index.d.ts +28 -37
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +28 -90
  72. package/dist/index.js.map +1 -1
  73. package/dist/judge-gate-C59znGmR.js +638 -0
  74. package/dist/judge-gate-C59znGmR.js.map +1 -0
  75. package/dist/judge-gate-D2hCppem.d.ts +439 -0
  76. package/dist/judge-gate-D2hCppem.d.ts.map +1 -0
  77. package/dist/{kind-factory-Cn1lbsAd.js → kind-factory-ivTx6koH.js} +2 -11
  78. package/dist/{kind-factory-Cn1lbsAd.js.map → kind-factory-ivTx6koH.js.map} +1 -1
  79. package/dist/{llm-judge-HLWASwUJ.js → llm-judge-Tr7MBUnf.js} +9 -9
  80. package/dist/llm-judge-Tr7MBUnf.js.map +1 -0
  81. package/dist/{matrix-Cqkk7eYI.d.ts → matrix-jHTsHEkR.d.ts} +2 -2
  82. package/dist/{matrix-Cqkk7eYI.d.ts.map → matrix-jHTsHEkR.d.ts.map} +1 -1
  83. package/dist/meta-eval/index.d.ts +3 -133
  84. package/dist/meta-eval/index.d.ts.map +1 -1
  85. package/dist/meta-eval/index.js +3 -276
  86. package/dist/meta-eval/index.js.map +1 -1
  87. package/dist/{mint-C1W8w5wo.d.ts → mint-CDy-brUR.d.ts} +2 -2
  88. package/dist/{mint-C1W8w5wo.d.ts.map → mint-CDy-brUR.d.ts.map} +1 -1
  89. package/dist/{mint-CWG-s2mI.js → mint-Csor1BLJ.js} +2 -2
  90. package/dist/{mint-CWG-s2mI.js.map → mint-Csor1BLJ.js.map} +1 -1
  91. package/dist/multishot/golden/index.d.ts +1 -1
  92. package/dist/multishot/index.d.ts +2 -2
  93. package/dist/openapi.json +1 -1
  94. package/dist/{paired-arms-CgWT7sHy.js → paired-arms-DzKn0TfY.js} +3 -5
  95. package/dist/{paired-arms-CgWT7sHy.js.map → paired-arms-DzKn0TfY.js.map} +1 -1
  96. package/dist/{paired-binary-b2L3WKqr.js → paired-binary-Q36bhn2R.js} +13 -9
  97. package/dist/paired-binary-Q36bhn2R.js.map +1 -0
  98. package/dist/{paired-promotion-decision-2deJ7sDX.js → paired-promotion-decision-BN6IQTiu.js} +2 -2
  99. package/dist/{paired-promotion-decision-2deJ7sDX.js.map → paired-promotion-decision-BN6IQTiu.js.map} +1 -1
  100. package/dist/pipelines/index.d.ts +1 -1
  101. package/dist/pipelines/index.js +2 -2
  102. package/dist/{prime-protocol-Csr9kcOM.d.ts → prime-protocol-B7et3tI7.d.ts} +2 -2
  103. package/dist/{prime-protocol-Csr9kcOM.d.ts.map → prime-protocol-B7et3tI7.d.ts.map} +1 -1
  104. package/dist/{produced-state-Bzlh09oj.js → produced-state-CKybuR9F.js} +3 -3
  105. package/dist/produced-state-CKybuR9F.js.map +1 -0
  106. package/dist/profile-cell.d.ts +2 -2
  107. package/dist/profile-cell.js +2 -2
  108. package/dist/{promotion-policy-CCmyEH_c.js → promotion-policy-CWW35UWq.js} +3 -3
  109. package/dist/{promotion-policy-CCmyEH_c.js.map → promotion-policy-CWW35UWq.js.map} +1 -1
  110. package/dist/{query-DlV0pdx4.js → query-B1D1qp2Z.js} +2 -2
  111. package/dist/{query-DlV0pdx4.js.map → query-B1D1qp2Z.js.map} +1 -1
  112. package/dist/{query-DBUQhehH.d.ts → query-DK1nWcmp.d.ts} +2 -3
  113. package/dist/{query-DBUQhehH.d.ts.map → query-DK1nWcmp.d.ts.map} +1 -1
  114. package/dist/{registry-DMkmhBQ5.d.ts → registry-BL7H33tj.d.ts} +3 -3
  115. package/dist/{registry-DMkmhBQ5.d.ts.map → registry-BL7H33tj.d.ts.map} +1 -1
  116. package/dist/{release-confidence-CkoXFx9I.d.ts → release-confidence-C4kI7lQi.d.ts} +4 -12
  117. package/dist/release-confidence-C4kI7lQi.d.ts.map +1 -0
  118. package/dist/{release-confidence-CiFg1EPq.js → release-confidence-Dbe9AS_X.js} +2 -2
  119. package/dist/{release-confidence-CiFg1EPq.js.map → release-confidence-Dbe9AS_X.js.map} +1 -1
  120. package/dist/{report-command-pXtxg5YK.js → report-command-CtIIsQKP.js} +3 -28
  121. package/dist/{report-command-pXtxg5YK.js.map → report-command-CtIIsQKP.js.map} +1 -1
  122. package/dist/reporting.d.ts +3 -3
  123. package/dist/reporting.js +3 -3
  124. package/dist/{researcher-CZFZjj-u.d.ts → researcher-DuIJ9IaA.d.ts} +4 -4
  125. package/dist/{researcher-CZFZjj-u.d.ts.map → researcher-DuIJ9IaA.d.ts.map} +1 -1
  126. package/dist/{reward-hacking-Cyk-QHk9.d.ts → reward-hacking-C6Kp54Av.d.ts} +2 -2
  127. package/dist/{reward-hacking-Cyk-QHk9.d.ts.map → reward-hacking-C6Kp54Av.d.ts.map} +1 -1
  128. package/dist/reward-nw2xZGZG.js.map +1 -1
  129. package/dist/rl.d.ts +10 -29
  130. package/dist/rl.d.ts.map +1 -1
  131. package/dist/rl.js +7 -7
  132. package/dist/rl.js.map +1 -1
  133. package/dist/rollout/index.d.ts +3 -3
  134. package/dist/rollout/index.js +4 -4
  135. package/dist/{rollout-l9w3Gdvh.js → rollout-D9bKKBnZ.js} +30 -165
  136. package/dist/rollout-D9bKKBnZ.js.map +1 -0
  137. package/dist/{rubric-predictive-validity-DIUwJJ9Y.d.ts → rubric-predictive-validity-BiNNEjpG.d.ts} +2 -2
  138. package/dist/{rubric-predictive-validity-DIUwJJ9Y.d.ts.map → rubric-predictive-validity-BiNNEjpG.d.ts.map} +1 -1
  139. package/dist/{run-record-BRlM99rX.js → run-record-DlTZ_lqe.js} +3 -13
  140. package/dist/run-record-DlTZ_lqe.js.map +1 -0
  141. package/dist/{run-record-BB290Y9A.js → run-record-ES07wfz9.js} +2 -2
  142. package/dist/{run-record-BB290Y9A.js.map → run-record-ES07wfz9.js.map} +1 -1
  143. package/dist/{run-record-DF-fuu31.d.ts → run-record-p2AYTrCn.d.ts} +3 -7
  144. package/dist/run-record-p2AYTrCn.d.ts.map +1 -0
  145. package/dist/search/index.d.ts +2 -24
  146. package/dist/search/index.d.ts.map +1 -1
  147. package/dist/search/index.js +3 -3
  148. package/dist/{search-kernel-CIie6xig.js → search-kernel-BIXWM7YV.js} +4 -4
  149. package/dist/search-kernel-BIXWM7YV.js.map +1 -0
  150. package/dist/{search-kernel-uKdPVRAe.d.ts → search-kernel-ah2GESe3.d.ts} +3 -3
  151. package/dist/{search-kernel-uKdPVRAe.d.ts.map → search-kernel-ah2GESe3.d.ts.map} +1 -1
  152. package/dist/{search-ledger-CXQPwdWz.js → search-ledger-aMTNQn_m.js} +2 -2
  153. package/dist/{search-ledger-CXQPwdWz.js.map → search-ledger-aMTNQn_m.js.map} +1 -1
  154. package/dist/{search-shipper-DhhofkDX.js → search-shipper-DKXWuLnp.js} +2 -2
  155. package/dist/{search-shipper-DhhofkDX.js.map → search-shipper-DKXWuLnp.js.map} +1 -1
  156. package/dist/{search-summary-BV5uehLd.js → search-summary-nwrtXxC_.js} +3 -3
  157. package/dist/{search-summary-BV5uehLd.js.map → search-summary-nwrtXxC_.js.map} +1 -1
  158. package/dist/{semantic-concept-judge-D1wH-YKc.js → semantic-concept-judge-BQxZyXdN.js} +3 -3
  159. package/dist/{semantic-concept-judge-D1wH-YKc.js.map → semantic-concept-judge-BQxZyXdN.js.map} +1 -1
  160. package/dist/{sequential-DMqr3sni.js → sequential-D-1PPCmM.js} +3 -3
  161. package/dist/{sequential-DMqr3sni.js.map → sequential-D-1PPCmM.js.map} +1 -1
  162. package/dist/{skillopt-optimization-method-Dcnh5Pvr.js → skillopt-optimization-method-DujPOiwD.js} +5 -5
  163. package/dist/{skillopt-optimization-method-Dcnh5Pvr.js.map → skillopt-optimization-method-DujPOiwD.js.map} +1 -1
  164. package/dist/{statistical-heldout-BP6iEJvR.js → statistical-heldout-1Gbmex7v.js} +2 -2
  165. package/dist/{statistical-heldout-BP6iEJvR.js.map → statistical-heldout-1Gbmex7v.js.map} +1 -1
  166. package/dist/{statistical-heldout-DFKtp-Qm.d.ts → statistical-heldout-8RGPIeyo.d.ts} +17 -14
  167. package/dist/statistical-heldout-8RGPIeyo.d.ts.map +1 -0
  168. package/dist/{store-otlp-BA7G1Du-.js → store-otlp-__ebQ6Hn.js} +2 -2
  169. package/dist/{store-otlp-BA7G1Du-.js.map → store-otlp-__ebQ6Hn.js.map} +1 -1
  170. package/dist/{store-tool-spans-DKoVbE-w.js → store-tool-spans-TbRBuIM7.js} +3 -3
  171. package/dist/{store-tool-spans-DKoVbE-w.js.map → store-tool-spans-TbRBuIM7.js.map} +1 -1
  172. package/dist/{store-tool-spans-9YJNMP-z.d.ts → store-tool-spans-wQ9idsnn.d.ts} +5 -5
  173. package/dist/{store-tool-spans-9YJNMP-z.d.ts.map → store-tool-spans-wQ9idsnn.d.ts.map} +1 -1
  174. package/dist/{summary-report-DvvesgR_.d.ts → summary-report-CK0ueoJd.d.ts} +2 -2
  175. package/dist/{summary-report-DvvesgR_.d.ts.map → summary-report-CK0ueoJd.d.ts.map} +1 -1
  176. package/dist/{summary-report-fXurE2TR.js → summary-report-DtdRucM0.js} +3 -3
  177. package/dist/{summary-report-fXurE2TR.js.map → summary-report-DtdRucM0.js.map} +1 -1
  178. package/dist/supervisor-run/index.d.ts +0 -8
  179. package/dist/supervisor-run/index.d.ts.map +1 -1
  180. package/dist/supervisor-run/index.js +36 -71
  181. package/dist/supervisor-run/index.js.map +1 -1
  182. package/dist/{systemone-review-Bt4JcxMc.d.ts → systemone-review-BReh3mvm.d.ts} +2 -2
  183. package/dist/{systemone-review-Bt4JcxMc.d.ts.map → systemone-review-BReh3mvm.d.ts.map} +1 -1
  184. package/dist/systemone.d.ts +3 -3
  185. package/dist/{tool-groups-j19pmOfP.d.ts → tool-groups-v28A-ZAF.d.ts} +3 -3
  186. package/dist/tool-groups-v28A-ZAF.d.ts.map +1 -0
  187. package/dist/{tool-waste-x_E8h9Ih.js → tool-waste-BYt5rdt8.js} +2 -2
  188. package/dist/{tool-waste-x_E8h9Ih.js.map → tool-waste-BYt5rdt8.js.map} +1 -1
  189. package/dist/{tool-waste-DxlebQkx.d.ts → tool-waste-Wi4g6Ekb.d.ts} +2 -2
  190. package/dist/{tool-waste-DxlebQkx.d.ts.map → tool-waste-Wi4g6Ekb.d.ts.map} +1 -1
  191. package/dist/{trace-questions-dRvmsbTk.d.ts → trace-questions-C26UvI3D.d.ts} +3 -3
  192. package/dist/{trace-questions-dRvmsbTk.d.ts.map → trace-questions-C26UvI3D.d.ts.map} +1 -1
  193. package/dist/trace-repair/index.d.ts +2 -9
  194. package/dist/trace-repair/index.d.ts.map +1 -1
  195. package/dist/trace-repair/index.js +1 -1
  196. package/dist/traces.d.ts +6 -8
  197. package/dist/traces.d.ts.map +1 -1
  198. package/dist/traces.js +6 -6
  199. package/dist/trajectory-replay/index.d.ts +0 -2
  200. package/dist/trajectory-replay/index.d.ts.map +1 -1
  201. package/dist/trajectory-replay/index.js +1 -1
  202. package/dist/{tree-CJOXbVcv.js → tree-kc1d80yF.js} +5 -25
  203. package/dist/tree-kc1d80yF.js.map +1 -0
  204. package/dist/{types-CFCUZk0B.d.ts → types-Dv9sYwBB.d.ts} +4 -4
  205. package/dist/{types-CFCUZk0B.d.ts.map → types-Dv9sYwBB.d.ts.map} +1 -1
  206. package/dist/{types-DAPpAkd4.d.ts → types-KEkKkETv.d.ts} +2 -2
  207. package/dist/{types-DAPpAkd4.d.ts.map → types-KEkKkETv.d.ts.map} +1 -1
  208. package/dist/{types-BlSBUNRd.d.ts → types-tWFY6bON.d.ts} +3 -3
  209. package/dist/{types-BlSBUNRd.d.ts.map → types-tWFY6bON.d.ts.map} +1 -1
  210. package/dist/wire/index.d.ts +1 -1
  211. package/docs/concepts.md +13 -0
  212. package/docs/experiment.md +10 -0
  213. package/docs/public-api.md +399 -424
  214. package/docs/trace-analysis.md +3 -0
  215. package/package.json +6 -5
  216. package/dist/agent-profile-cell-BHcB-mZX.d.ts.map +0 -1
  217. package/dist/agent-profile-cell-DcFW4vOc.js.map +0 -1
  218. package/dist/analyze-runs-GZ0VSwui.js.map +0 -1
  219. package/dist/campaign-evidence-DW3Krxov.js.map +0 -1
  220. package/dist/claude-jsonl-CG3JUbTJ.js +0 -211
  221. package/dist/claude-jsonl-CG3JUbTJ.js.map +0 -1
  222. package/dist/engine-DProXJ7A.d.ts.map +0 -1
  223. package/dist/llm-judge-HLWASwUJ.js.map +0 -1
  224. package/dist/paired-binary-b2L3WKqr.js.map +0 -1
  225. package/dist/produced-state-Bzlh09oj.js.map +0 -1
  226. package/dist/release-confidence-CkoXFx9I.d.ts.map +0 -1
  227. package/dist/rollout-l9w3Gdvh.js.map +0 -1
  228. package/dist/run-record-BRlM99rX.js.map +0 -1
  229. package/dist/run-record-DF-fuu31.d.ts.map +0 -1
  230. package/dist/search-kernel-CIie6xig.js.map +0 -1
  231. package/dist/series-convergence-CjO2QdRW.js +0 -43
  232. package/dist/series-convergence-CjO2QdRW.js.map +0 -1
  233. package/dist/series-convergence-hEfKdoi-.d.ts +0 -126
  234. package/dist/series-convergence-hEfKdoi-.d.ts.map +0 -1
  235. package/dist/statistical-heldout-DFKtp-Qm.d.ts.map +0 -1
  236. package/dist/tool-groups-j19pmOfP.d.ts.map +0 -1
  237. package/dist/tree-CJOXbVcv.js.map +0 -1
@@ -0,0 +1,638 @@
1
+ import { i as JudgeError, s as ValidationError } from "./errors-Dngq5h35.js";
2
+ import { r as computeInterval } from "./ast-DJ6Bc7ab.js";
3
+ //#region src/series-convergence.ts
4
+ function analyzeSeries(values, options = {}) {
5
+ const window = options.window ?? 5;
6
+ const stableCv = options.stableCv ?? .05;
7
+ const driftRun = options.driftRun ?? 3;
8
+ if (values.length < Math.max(2, Math.min(window, 3))) return {
9
+ state: "insufficient-data",
10
+ windowMean: 0,
11
+ windowCv: 0,
12
+ tailRun: 0,
13
+ stable: false
14
+ };
15
+ const tail = values.slice(-window);
16
+ const mean = tail.reduce((a, b) => a + b, 0) / tail.length;
17
+ const variance = tail.reduce((acc, v) => acc + (v - mean) ** 2, 0) / tail.length;
18
+ const cv = Math.sqrt(variance) / (Math.abs(mean) > 1e-9 ? Math.abs(mean) : 1);
19
+ const stable = tail.length >= window && cv <= stableCv;
20
+ let tailRun = 0;
21
+ let direction = 0;
22
+ for (let i = values.length - 1; i > 0; i--) {
23
+ const delta = values[i] - values[i - 1];
24
+ if (delta === 0) break;
25
+ const dir = delta > 0 ? 1 : -1;
26
+ if (direction === 0) direction = dir;
27
+ if (dir !== direction) break;
28
+ tailRun += dir;
29
+ }
30
+ let state;
31
+ if (stable) state = "stabilized";
32
+ else if (Math.abs(tailRun) >= driftRun) state = tailRun > 0 ? "drifting-up" : "drifting-down";
33
+ else state = "noisy";
34
+ return {
35
+ state,
36
+ windowMean: mean,
37
+ windowCv: cv,
38
+ tailRun,
39
+ stable
40
+ };
41
+ }
42
+ //#endregion
43
+ //#region src/meta-eval/sentinel.ts
44
+ /**
45
+ * Judge sentinel — eval trustworthiness as a continuously measured,
46
+ * alarmed trend.
47
+ *
48
+ * Judges are models; models change underneath us; calibration decays.
49
+ * This module composes three existing instruments into one loop:
50
+ *
51
+ * snapshot — adapters turn real instrument outputs (`calibrateJudge` /
52
+ * `calibrateJudgeContinuous`, `corpusInterRaterAgreement` /
53
+ * `continuousAgreement`, a rerun sentinel golden set) into
54
+ * `SentinelSnapshot`s
55
+ * series — `analyzeSeries` (src/series-convergence) classifies each
56
+ * judge×metric history as stabilized / drifting / noisy
57
+ * alarm — `judgeSentinelReport` turns trends + thresholds into named
58
+ * alarms; `evalHealthStamp` is the tiny object a campaign
59
+ * attaches to its verdicts
60
+ *
61
+ * Alarm conditions:
62
+ * - convergence state `drifting-down` on any tracked metric
63
+ * - irr below `minIrr`
64
+ * - calibrationKappa / sentinelPassRate dropped vs the series baseline
65
+ * beyond `maxKappaDrop` / `maxSentinelDrop`
66
+ * - judge model changed with no post-change golden-grounded snapshot
67
+ * (the silent-upgrade trap: judges agreeing with each other after a
68
+ * model swap proves nothing — only re-measuring against gold does)
69
+ * - newest snapshot older than `staleAfterDays` relative to `asOf`
70
+ *
71
+ * Wire-in: run the snapshot adapters from a post-campaign hook or a
72
+ * nightly job, append to a `SentinelStore`, then compute
73
+ * `judgeSentinelReport(await store.history(), { asOf })` with `asOf` =
74
+ * the campaign timestamp. Attach `evalHealthStamp(report)` to campaign
75
+ * verdicts: an alarmed sentinel means every downstream verdict carries
76
+ * an integrity warning until the judge is recalibrated.
77
+ *
78
+ * The module is clock-free — every timestamp (`at`, `asOf`) is
79
+ * caller-supplied ISO-8601, so reports are reproducible.
80
+ */
81
+ const SENTINEL_METRIC_NAMES = [
82
+ "irr",
83
+ "calibrationKappa",
84
+ "sentinelPassRate"
85
+ ];
86
+ function parseIso$1(value, label) {
87
+ const ms = typeof value === "string" ? Date.parse(value) : NaN;
88
+ if (!Number.isFinite(ms)) throw new ValidationError(`judge-sentinel: ${label} is not a parseable ISO timestamp: ${JSON.stringify(value)}`);
89
+ return ms;
90
+ }
91
+ /** Shape guard for snapshots crossing a boundary (store append, JSONL load, report input). */
92
+ function validateSentinelSnapshot(snapshot, source = "snapshot") {
93
+ parseIso$1(snapshot.at, `${source}.at`);
94
+ if (typeof snapshot.judgeId !== "string" || snapshot.judgeId.length === 0) throw new ValidationError(`judge-sentinel: ${source}.judgeId must be a non-empty string`);
95
+ if (typeof snapshot.judgeModel !== "string" || snapshot.judgeModel.length === 0) throw new ValidationError(`judge-sentinel: ${source}.judgeModel must be a non-empty string`);
96
+ if (snapshot.metrics === null || typeof snapshot.metrics !== "object") throw new ValidationError(`judge-sentinel: ${source}.metrics must be an object (may be empty)`);
97
+ for (const [key, value] of Object.entries(snapshot.metrics)) {
98
+ if (!SENTINEL_METRIC_NAMES.includes(key)) throw new ValidationError(`judge-sentinel: ${source}.metrics carries unknown key "${key}" — known: ${SENTINEL_METRIC_NAMES.join(", ")}`);
99
+ if (value !== void 0 && !Number.isFinite(value)) throw new ValidationError(`judge-sentinel: ${source}.metrics.${key} is not a finite number: ${String(value)}`);
100
+ }
101
+ }
102
+ /**
103
+ * From `calibrateJudge` / `calibrateJudgeContinuous` output. Prefers the
104
+ * un-rounded continuous κ_w when the report carries one (integer κ
105
+ * discards information for [0,1] judges).
106
+ */
107
+ function snapshotFromCalibration(report, meta) {
108
+ const kappa = "weightedKappaContinuous" in report ? report.weightedKappaContinuous : report.kappa;
109
+ if (!Number.isFinite(kappa)) throw new ValidationError(`snapshotFromCalibration: κ is not finite (n=${report.n}) — calibrate against ≥2 joined golden items before snapshotting`);
110
+ const snapshot = {
111
+ ...meta,
112
+ metrics: { calibrationKappa: kappa }
113
+ };
114
+ validateSentinelSnapshot(snapshot);
115
+ return snapshot;
116
+ }
117
+ /**
118
+ * From `corpusInterRaterAgreement` (statistics.ts) or `continuousAgreement`
119
+ * (judge-calibration.ts) output. IRR = ICC(2,1) — the reliability
120
+ * coefficient both instruments compute. For ensemble-level agreement,
121
+ * `meta.judgeId` names the ensemble and `meta.judgeModel` pins its
122
+ * composition (e.g. a joined list of member model strings).
123
+ */
124
+ function snapshotFromAgreement(report, meta) {
125
+ const irr = "overallIcc" in report ? report.overallIcc : report.icc;
126
+ if (!Number.isFinite(irr)) throw new ValidationError("snapshotFromAgreement: ICC is not finite — agreement needs ≥2 raters and ≥2 complete items");
127
+ const snapshot = {
128
+ ...meta,
129
+ metrics: { irr }
130
+ };
131
+ validateSentinelSnapshot(snapshot);
132
+ return snapshot;
133
+ }
134
+ /**
135
+ * From a rerun of a fixed sentinel golden set: the same `GoldenItem`s the
136
+ * judge was originally calibrated on, re-scored periodically. Pass rate =
137
+ * fraction of joined items where the judge stays within `tolerance` of the
138
+ * human score. Items the judge didn't score are excluded from the
139
+ * denominator; zero joined items is an error, not a 0% pass rate.
140
+ */
141
+ function snapshotFromSentinelSet(scores, golden, meta, options = {}) {
142
+ const tolerance = options.tolerance ?? .1;
143
+ if (!Number.isFinite(tolerance) || tolerance < 0) throw new ValidationError(`snapshotFromSentinelSet: tolerance must be a finite number ≥ 0, got ${tolerance}`);
144
+ const goldenById = /* @__PURE__ */ new Map();
145
+ for (const item of golden) {
146
+ if (!Number.isFinite(item.humanScore)) throw new ValidationError(`snapshotFromSentinelSet: golden item "${item.itemId}" has a non-finite humanScore`);
147
+ if (goldenById.has(item.itemId)) throw new ValidationError(`snapshotFromSentinelSet: duplicate golden itemId "${item.itemId}"`);
148
+ goldenById.set(item.itemId, item.humanScore);
149
+ }
150
+ let joined = 0;
151
+ let passed = 0;
152
+ for (const s of scores) {
153
+ const human = goldenById.get(s.itemId);
154
+ if (human === void 0) continue;
155
+ if (!Number.isFinite(s.score)) throw new ValidationError(`snapshotFromSentinelSet: judge score for item "${s.itemId}" is not finite`);
156
+ joined++;
157
+ if (Math.abs(s.score - human) <= tolerance) passed++;
158
+ }
159
+ if (joined === 0) throw new ValidationError("snapshotFromSentinelSet: no judge scores joined the golden set — itemIds mismatch or empty sentinel run");
160
+ const snapshot = {
161
+ ...meta,
162
+ metrics: { sentinelPassRate: passed / joined }
163
+ };
164
+ validateSentinelSnapshot(snapshot);
165
+ return snapshot;
166
+ }
167
+ function inMemorySentinelStore(initial = []) {
168
+ for (const s of initial) validateSentinelSnapshot(s);
169
+ const items = initial.map((s) => ({
170
+ ...s,
171
+ metrics: { ...s.metrics }
172
+ }));
173
+ return {
174
+ async append(snapshot) {
175
+ validateSentinelSnapshot(snapshot);
176
+ items.push({
177
+ ...snapshot,
178
+ metrics: { ...snapshot.metrics }
179
+ });
180
+ },
181
+ async history(judgeId) {
182
+ const all = items.map((s) => ({
183
+ ...s,
184
+ metrics: { ...s.metrics }
185
+ }));
186
+ return judgeId === void 0 ? all : all.filter((s) => s.judgeId === judgeId);
187
+ }
188
+ };
189
+ }
190
+ /**
191
+ * JSONL store — one snapshot per line, appended atomically per call.
192
+ * A corrupt or shape-invalid line is a loud error naming the file and
193
+ * line number: a sentinel history that silently drops records would
194
+ * defeat the drift detection it exists to provide.
195
+ */
196
+ function fileSentinelStore(path) {
197
+ return {
198
+ async append(snapshot) {
199
+ validateSentinelSnapshot(snapshot);
200
+ const fs = await import("node:fs/promises");
201
+ const pathMod = await import("node:path");
202
+ await fs.mkdir(pathMod.dirname(path), { recursive: true });
203
+ await fs.appendFile(path, `${JSON.stringify(snapshot)}\n`, "utf8");
204
+ },
205
+ async history(judgeId) {
206
+ const fs = await import("node:fs/promises");
207
+ let raw;
208
+ try {
209
+ raw = await fs.readFile(path, "utf8");
210
+ } catch (err) {
211
+ if (err.code === "ENOENT") return [];
212
+ throw err;
213
+ }
214
+ const lines = raw.split("\n");
215
+ const snapshots = [];
216
+ for (let i = 0; i < lines.length; i++) {
217
+ const line = lines[i];
218
+ if (line.trim().length === 0) continue;
219
+ let parsed;
220
+ try {
221
+ parsed = JSON.parse(line);
222
+ } catch (err) {
223
+ throw new ValidationError(`judge-sentinel: store at ${path} has a corrupt JSONL line ${i + 1}: ${err.message}`);
224
+ }
225
+ const snapshot = parsed;
226
+ validateSentinelSnapshot(snapshot, `${path}:${i + 1}`);
227
+ snapshots.push(snapshot);
228
+ }
229
+ return judgeId === void 0 ? snapshots : snapshots.filter((s) => s.judgeId === judgeId);
230
+ }
231
+ };
232
+ }
233
+ const MS_PER_DAY = 864e5;
234
+ function judgeSentinelReport(history, opts) {
235
+ const asOfMs = parseIso$1(opts.asOf, "asOf");
236
+ const minIrr = opts.thresholds?.minIrr ?? .6;
237
+ const maxKappaDrop = opts.thresholds?.maxKappaDrop ?? .15;
238
+ const maxSentinelDrop = opts.thresholds?.maxSentinelDrop ?? .1;
239
+ const staleAfterDays = opts.thresholds?.staleAfterDays ?? 30;
240
+ const byJudge = /* @__PURE__ */ new Map();
241
+ for (const snapshot of history) {
242
+ validateSentinelSnapshot(snapshot);
243
+ const atMs = parseIso$1(snapshot.at, `snapshot.at (judge "${snapshot.judgeId}")`);
244
+ const arr = byJudge.get(snapshot.judgeId) ?? [];
245
+ arr.push({
246
+ ...snapshot,
247
+ atMs
248
+ });
249
+ byJudge.set(snapshot.judgeId, arr);
250
+ }
251
+ const perJudge = [];
252
+ const alarms = [];
253
+ const insufficientHistory = [];
254
+ for (const judgeId of [...byJudge.keys()].sort()) {
255
+ const snaps = byJudge.get(judgeId).sort((a, b) => a.atMs - b.atMs);
256
+ const newest = snaps[snaps.length - 1];
257
+ const ageDays = (asOfMs - newest.atMs) / MS_PER_DAY;
258
+ if (ageDays > staleAfterDays) alarms.push(`judge "${judgeId}": stale — newest snapshot ${newest.at} is ${ageDays.toFixed(1)}d old vs asOf (limit ${staleAfterDays}d)`);
259
+ let changeIdx = -1;
260
+ for (let i = 1; i < snaps.length; i++) if (snaps[i].judgeModel !== snaps[i - 1].judgeModel) changeIdx = i;
261
+ if (changeIdx >= 0) {
262
+ if (!snaps.slice(changeIdx).some((s) => Number.isFinite(s.metrics.calibrationKappa) || Number.isFinite(s.metrics.sentinelPassRate))) alarms.push(`judge "${judgeId}": model changed "${snaps[changeIdx - 1].judgeModel}" → "${snaps[changeIdx].judgeModel}" at ${snaps[changeIdx].at} with no post-change calibration snapshot — silent-upgrade trap; rerun calibrateJudge or the sentinel set against the new model`);
263
+ }
264
+ for (const metric of SENTINEL_METRIC_NAMES) {
265
+ const values = snaps.filter((s) => typeof s.metrics[metric] === "number").map((s) => s.metrics[metric]);
266
+ if (values.length === 0) continue;
267
+ const convergence = analyzeSeries(values, opts.convergence);
268
+ if (convergence.state === "insufficient-data") insufficientHistory.push(`${judgeId}:${metric}`);
269
+ const current = values[values.length - 1];
270
+ const baseline = values[0];
271
+ const drift = current - baseline;
272
+ const reasons = [];
273
+ if (convergence.state === "drifting-down") reasons.push(`convergence state drifting-down (tail run ${convergence.tailRun}, window mean ${convergence.windowMean.toFixed(3)})`);
274
+ if (metric === "irr" && current < minIrr) reasons.push(`irr ${current.toFixed(3)} below floor ${minIrr}`);
275
+ if (metric === "calibrationKappa" && baseline - current > maxKappaDrop) reasons.push(`calibrationKappa dropped ${(baseline - current).toFixed(3)} from baseline ${baseline.toFixed(3)} (limit ${maxKappaDrop})`);
276
+ if (metric === "sentinelPassRate" && baseline - current > maxSentinelDrop) reasons.push(`sentinelPassRate dropped ${(baseline - current).toFixed(3)} from baseline ${baseline.toFixed(3)} (limit ${maxSentinelDrop})`);
277
+ const alarmed = reasons.length > 0;
278
+ const trend = {
279
+ judgeId,
280
+ metric,
281
+ state: convergence.state,
282
+ current,
283
+ baseline,
284
+ drift,
285
+ alarmed
286
+ };
287
+ if (alarmed) {
288
+ trend.reason = reasons.join("; ");
289
+ alarms.push(`judge "${judgeId}" ${metric}: ${trend.reason}`);
290
+ }
291
+ perJudge.push(trend);
292
+ }
293
+ }
294
+ return {
295
+ perJudge,
296
+ alarms,
297
+ healthy: alarms.length === 0,
298
+ insufficientHistory
299
+ };
300
+ }
301
+ /**
302
+ * The object a campaign attaches to its verdicts. Compute it from the
303
+ * sentinel report in a post-campaign hook (or a nightly job feeding the
304
+ * next day's campaigns) and store it alongside the verdict payload.
305
+ * `healthy: false` means the judges that produced those verdicts have an
306
+ * unresolved drift / decay / silent-upgrade / staleness alarm — treat the
307
+ * verdicts as carrying an integrity warning until recalibration clears it.
308
+ */
309
+ function evalHealthStamp(report) {
310
+ return {
311
+ healthy: report.healthy,
312
+ alarms: [...report.alarms]
313
+ };
314
+ }
315
+ //#endregion
316
+ //#region src/meta-eval/judge-gate.ts
317
+ /**
318
+ * Judge calibration gate: a judge may gate only while its measured agreement
319
+ * with its owner's verdicts holds.
320
+ *
321
+ * A judge (a model judge or a deterministic grader) registers a calibration
322
+ * set: items its owner has decided, each verdict with its provenance. A
323
+ * calibration run replays the judge on those items, once or several times, and
324
+ * `measureJudgeAgreement` reports how often it reached the owner's verdict
325
+ * (accuracy, and Cohen's κ for the pass/fail pair), how much its own verdict
326
+ * moves between runs and between samples, and where it disagreed.
327
+ *
328
+ * `judgeGateDecision` turns that report into the one decision a consumer acts
329
+ * on. It refuses when the judge has no measured agreement, too few decided
330
+ * items of either verdict, a report measured on a different model or rubric
331
+ * than the one the judge now runs, a report older than the policy allows,
332
+ * agreement below the policy floors, or an alarmed drift history. A refused
333
+ * judge's verdict is advisory: the consumer reports it but does not let it
334
+ * pass or block anything.
335
+ *
336
+ * Drift is the judge sentinel (`./sentinel`): `snapshotFromJudgeAgreement`
337
+ * records accuracy as `sentinelPassRate` and κ as `calibrationKappa`, so each
338
+ * calibration run appends one snapshot to a `SentinelStore` and the gate reads
339
+ * the alarms `judgeSentinelReport` raises over that history.
340
+ *
341
+ * Every timestamp is caller-supplied ISO-8601, so decisions are reproducible.
342
+ */
343
+ function parseIso(value, label) {
344
+ const ms = typeof value === "string" ? Date.parse(value) : NaN;
345
+ if (!Number.isFinite(ms)) throw new ValidationError(`judge-gate: ${label} is not a parseable ISO timestamp: ${JSON.stringify(value)}`);
346
+ return ms;
347
+ }
348
+ function requireText(value, label) {
349
+ if (typeof value !== "string" || value.trim().length === 0) throw new ValidationError(`judge-gate: ${label} must be a non-empty string`);
350
+ return value;
351
+ }
352
+ function requireVerdict(value, label) {
353
+ if (value !== "pass" && value !== "fail") throw new ValidationError(`judge-gate: ${label} must be 'pass' or 'fail', got ${JSON.stringify(value)}`);
354
+ return value;
355
+ }
356
+ function checkExample(example, label) {
357
+ if (example === null || typeof example !== "object") throw new ValidationError(`judge-gate: ${label} must be an object`);
358
+ requireText(example.id, `${label}.id`);
359
+ requireVerdict(example.verdict, `${label}.verdict`);
360
+ requireText(example.labeledBy, `${label}.labeledBy`);
361
+ parseIso(example.labeledAt, `${label}.labeledAt`);
362
+ requireText(example.source, `${label}.source`);
363
+ if (example.evidenceRef !== void 0) requireText(example.evidenceRef, `${label}.evidenceRef`);
364
+ const copy = {
365
+ id: example.id,
366
+ verdict: example.verdict,
367
+ labeledBy: example.labeledBy,
368
+ labeledAt: example.labeledAt,
369
+ source: example.source
370
+ };
371
+ if (example.evidenceRef !== void 0) copy.evidenceRef = example.evidenceRef;
372
+ if (example.note !== void 0) copy.note = example.note;
373
+ return copy;
374
+ }
375
+ /**
376
+ * Validate a calibration set and return a normalized copy ordered by example
377
+ * id. Every verdict needs its person, time and source; ids are unique.
378
+ */
379
+ function registerCalibrationSet(set) {
380
+ if (set === null || typeof set !== "object") throw new ValidationError("judge-gate: calibration set must be an object");
381
+ const judgeId = requireText(set.judgeId, "calibration set judgeId");
382
+ const owner = requireText(set.owner, `calibration set "${judgeId}" owner`);
383
+ if (!Array.isArray(set.examples)) throw new ValidationError(`judge-gate: calibration set "${judgeId}" examples must be an array`);
384
+ const seen = /* @__PURE__ */ new Set();
385
+ const examples = set.examples.map((example, index) => {
386
+ const checked = checkExample(example, `calibration set "${judgeId}" example ${index}`);
387
+ if (seen.has(checked.id)) throw new ValidationError(`judge-gate: calibration set "${judgeId}" has duplicate example id "${checked.id}"`);
388
+ seen.add(checked.id);
389
+ return checked;
390
+ });
391
+ examples.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
392
+ return {
393
+ judgeId,
394
+ owner,
395
+ examples
396
+ };
397
+ }
398
+ /**
399
+ * Join new owner verdicts to a set. A verdict already recorded from the same
400
+ * source is unchanged, so ingestion can rerun. A later verdict on an item
401
+ * replaces an earlier one: the owner changed their mind, and the set keeps
402
+ * what they decided last.
403
+ */
404
+ function addCalibrationVerdicts(set, verdicts) {
405
+ const base = registerCalibrationSet(set);
406
+ const byId = new Map(base.examples.map((example) => [example.id, example]));
407
+ const added = [];
408
+ const superseded = [];
409
+ const unchanged = [];
410
+ for (const [index, verdict] of verdicts.entries()) {
411
+ const incoming = checkExample(verdict, `incoming verdict ${index} for "${base.judgeId}"`);
412
+ const current = byId.get(incoming.id);
413
+ if (!current) {
414
+ byId.set(incoming.id, incoming);
415
+ added.push(incoming.id);
416
+ continue;
417
+ }
418
+ if (current.source === incoming.source && current.verdict === incoming.verdict || parseIso(incoming.labeledAt, "labeledAt") <= parseIso(current.labeledAt, "labeledAt")) {
419
+ unchanged.push(incoming.id);
420
+ continue;
421
+ }
422
+ byId.set(incoming.id, incoming);
423
+ superseded.push(incoming.id);
424
+ }
425
+ return {
426
+ set: registerCalibrationSet({
427
+ ...base,
428
+ examples: [...byId.values()]
429
+ }),
430
+ added,
431
+ superseded,
432
+ unchanged
433
+ };
434
+ }
435
+ function bernoulliVariance(passes, n) {
436
+ const p = passes / n;
437
+ return p * (1 - p);
438
+ }
439
+ /** Cohen's κ for two raters over a binary pass/fail table. */
440
+ function binaryKappa(confusion) {
441
+ const n = confusion.truePass + confusion.trueFail + confusion.falsePass + confusion.falseFail;
442
+ if (n === 0) return null;
443
+ const observed = (confusion.truePass + confusion.trueFail) / n;
444
+ const judgePass = (confusion.truePass + confusion.falsePass) / n;
445
+ const ownerPass = (confusion.truePass + confusion.falseFail) / n;
446
+ const expected = judgePass * ownerPass + (1 - judgePass) * (1 - ownerPass);
447
+ if (expected === 1) return null;
448
+ return (observed - expected) / (1 - expected);
449
+ }
450
+ /**
451
+ * Agreement between a judge's runs and the owner's verdicts on one
452
+ * calibration set. A run that names an example outside the set is an error:
453
+ * a silently dropped run would overstate agreement.
454
+ */
455
+ function measureJudgeAgreement(set, runs, meta) {
456
+ const registered = registerCalibrationSet(set);
457
+ if (meta.judgeId !== registered.judgeId) throw new ValidationError(`judge-gate: runs are for judge "${meta.judgeId}" but the set belongs to "${registered.judgeId}"`);
458
+ requireText(meta.judgeModel, "judgeModel");
459
+ requireText(meta.rubricVersion, "rubricVersion");
460
+ parseIso(meta.measuredAt, "measuredAt");
461
+ const byId = new Map(registered.examples.map((example) => [example.id, example]));
462
+ const runsById = /* @__PURE__ */ new Map();
463
+ const confusion = {
464
+ truePass: 0,
465
+ trueFail: 0,
466
+ falsePass: 0,
467
+ falseFail: 0
468
+ };
469
+ const withinRun = [];
470
+ for (const [index, run] of runs.entries()) {
471
+ const example = byId.get(run?.exampleId);
472
+ if (!example) throw new ValidationError(`judge-gate: run ${index} names example ${JSON.stringify(run?.exampleId)}, which is not in "${registered.judgeId}"'s calibration set`);
473
+ const verdict = requireVerdict(run.verdict, `run ${index} verdict`);
474
+ if (run.samples !== void 0) {
475
+ if (!Array.isArray(run.samples) || run.samples.length === 0) throw new ValidationError(`judge-gate: run ${index} samples must be a non-empty array`);
476
+ const samples = run.samples.map((sample, s) => requireVerdict(sample, `run ${index} sample ${s}`));
477
+ withinRun.push(bernoulliVariance(samples.filter((sample) => sample === "pass").length, samples.length));
478
+ }
479
+ const list = runsById.get(example.id) ?? [];
480
+ list.push({
481
+ ...run,
482
+ verdict
483
+ });
484
+ runsById.set(example.id, list);
485
+ if (verdict === "pass") {
486
+ if (example.verdict === "pass") confusion.truePass++;
487
+ else confusion.falsePass++;
488
+ } else if (example.verdict === "fail") confusion.trueFail++;
489
+ else confusion.falseFail++;
490
+ }
491
+ const perExample = [];
492
+ const unjudged = [];
493
+ const judgedByVerdict = {
494
+ pass: 0,
495
+ fail: 0
496
+ };
497
+ for (const example of registered.examples) {
498
+ const list = runsById.get(example.id);
499
+ if (!list) {
500
+ unjudged.push(example.id);
501
+ continue;
502
+ }
503
+ judgedByVerdict[example.verdict]++;
504
+ const passes = list.filter((run) => run.verdict === "pass").length;
505
+ perExample.push({
506
+ id: example.id,
507
+ verdict: example.verdict,
508
+ runs: list.length,
509
+ passRate: passes / list.length,
510
+ agreement: list.filter((run) => run.verdict === example.verdict).length / list.length
511
+ });
512
+ }
513
+ const total = runs.length;
514
+ const agreedRuns = confusion.truePass + confusion.trueFail;
515
+ const agreedExamples = perExample.filter((example) => example.agreement === 1).length;
516
+ const mean = (values) => values.length === 0 ? null : values.reduce((sum, value) => sum + value, 0) / values.length;
517
+ return {
518
+ ...meta,
519
+ owner: registered.owner,
520
+ examples: registered.examples.length,
521
+ judged: perExample.length,
522
+ judgedByVerdict,
523
+ runs: total,
524
+ accuracy: total === 0 ? null : agreedRuns / total,
525
+ exampleAccuracy: perExample.length === 0 ? null : {
526
+ agreed: agreedExamples,
527
+ judged: perExample.length,
528
+ interval: computeInterval({
529
+ kind: "clopper-pearson",
530
+ level: .95
531
+ }, {
532
+ kind: "binomial",
533
+ successes: agreedExamples,
534
+ trials: perExample.length
535
+ })
536
+ },
537
+ kappa: binaryKappa(confusion),
538
+ confusion,
539
+ variance: {
540
+ betweenRuns: mean(perExample.map((example) => example.passRate * (1 - example.passRate))),
541
+ withinRun: mean(withinRun),
542
+ unstable: perExample.filter((example) => example.passRate > 0 && example.passRate < 1).map((example) => example.id)
543
+ },
544
+ disagreements: perExample.filter((example) => example.agreement < 1).sort((a, b) => a.agreement - b.agreement || (a.id < b.id ? -1 : 1)),
545
+ unjudged
546
+ };
547
+ }
548
+ const DEFAULT_JUDGE_GATE_POLICY = Object.freeze({
549
+ minExamples: 10,
550
+ minPerVerdict: 1,
551
+ minAccuracy: .9,
552
+ minKappa: .6,
553
+ maxAgeDays: 30
554
+ });
555
+ const DAY_MS = 864e5;
556
+ function percent(value) {
557
+ return `${(value * 100).toFixed(1)}%`;
558
+ }
559
+ /** Whether a judge may gate now, and every reason it may not. */
560
+ function judgeGateDecision(input) {
561
+ const judgeId = requireText(input.judgeId, "judgeId");
562
+ const judgeModel = requireText(input.judgeModel, "judgeModel");
563
+ const rubricVersion = requireText(input.rubricVersion, "rubricVersion");
564
+ const asOf = parseIso(input.asOf, "asOf");
565
+ const policy = {
566
+ ...DEFAULT_JUDGE_GATE_POLICY,
567
+ ...input.policy
568
+ };
569
+ const agreement = input.agreement;
570
+ const decide = (status, reasons) => ({
571
+ judgeId,
572
+ judgeModel,
573
+ rubricVersion,
574
+ mayGate: status === "calibrated",
575
+ status,
576
+ reasons,
577
+ agreement,
578
+ policy
579
+ });
580
+ if (!agreement) return decide("uncalibrated", ["no measured agreement with owner verdicts"]);
581
+ if (agreement.judgeId !== judgeId) throw new ValidationError(`judge-gate: agreement belongs to "${agreement.judgeId}", not "${judgeId}"`);
582
+ const uncalibrated = [];
583
+ if (agreement.judgeModel !== judgeModel) uncalibrated.push(`agreement was measured on model ${agreement.judgeModel}; the judge now runs ${judgeModel}`);
584
+ if (agreement.rubricVersion !== rubricVersion) uncalibrated.push(`agreement was measured on rubric ${agreement.rubricVersion}; the judge now runs ${rubricVersion}`);
585
+ const ageDays = (asOf - parseIso(agreement.measuredAt, "agreement.measuredAt")) / DAY_MS;
586
+ if (ageDays > policy.maxAgeDays) uncalibrated.push(`agreement is ${ageDays.toFixed(1)} days old (limit ${policy.maxAgeDays})`);
587
+ if (agreement.judged < policy.minExamples) uncalibrated.push(`${agreement.judged} judged ${agreement.judged === 1 ? "example" : "examples"}; at least ${policy.minExamples} required`);
588
+ for (const verdict of ["pass", "fail"]) if (agreement.judgedByVerdict[verdict] < policy.minPerVerdict) uncalibrated.push(`${agreement.judgedByVerdict[verdict]} judged examples the owner marked ${verdict}; at least ${policy.minPerVerdict} required`);
589
+ if (uncalibrated.length > 0) return decide("uncalibrated", uncalibrated);
590
+ const below = [];
591
+ if (agreement.accuracy === null || agreement.accuracy < policy.minAccuracy) below.push(`accuracy ${agreement.accuracy === null ? "unmeasured" : percent(agreement.accuracy)} is below ${percent(policy.minAccuracy)}`);
592
+ if (agreement.kappa === null || agreement.kappa < policy.minKappa) below.push(`kappa ${agreement.kappa === null ? "undefined" : agreement.kappa.toFixed(2)} is below ${policy.minKappa}`);
593
+ if (below.length > 0) return decide("below-threshold", below);
594
+ const history = (input.history ?? []).filter((snapshot) => snapshot.judgeId === judgeId);
595
+ if (history.length > 0) {
596
+ const report = judgeSentinelReport([...history], {
597
+ asOf: input.asOf,
598
+ ...policy.drift ? { thresholds: policy.drift } : {}
599
+ });
600
+ if (!report.healthy) return decide("drifted", report.alarms);
601
+ }
602
+ return decide("calibrated", []);
603
+ }
604
+ /** Thrown by `assertJudgeMayGate`; carries the refused decision. */
605
+ var JudgeGateRefusedError = class extends JudgeError {
606
+ decision;
607
+ constructor(decision) {
608
+ super(`judge "${decision.judgeId}" may not gate (${decision.status}): ${decision.reasons.join("; ")}`);
609
+ this.decision = decision;
610
+ }
611
+ };
612
+ /** Throw unless the decision lets the judge gate. */
613
+ function assertJudgeMayGate(decision) {
614
+ if (!decision.mayGate) throw new JudgeGateRefusedError(decision);
615
+ }
616
+ /**
617
+ * The sentinel snapshot for one calibration run: accuracy as
618
+ * `sentinelPassRate`, κ as `calibrationKappa` when it is defined. Append it to
619
+ * the judge's `SentinelStore`; the gate reads drift from that history.
620
+ */
621
+ function snapshotFromJudgeAgreement(agreement) {
622
+ if (agreement.accuracy === null) throw new ValidationError(`snapshotFromJudgeAgreement: judge "${agreement.judgeId}" has no runs to snapshot`);
623
+ const snapshot = {
624
+ at: agreement.measuredAt,
625
+ judgeId: agreement.judgeId,
626
+ judgeModel: agreement.judgeModel,
627
+ metrics: {
628
+ sentinelPassRate: agreement.accuracy,
629
+ ...agreement.kappa === null ? {} : { calibrationKappa: agreement.kappa }
630
+ }
631
+ };
632
+ validateSentinelSnapshot(snapshot);
633
+ return snapshot;
634
+ }
635
+ //#endregion
636
+ export { analyzeSeries as _, judgeGateDecision as a, snapshotFromJudgeAgreement as c, inMemorySentinelStore as d, judgeSentinelReport as f, validateSentinelSnapshot as g, snapshotFromSentinelSet as h, assertJudgeMayGate as i, evalHealthStamp as l, snapshotFromCalibration as m, JudgeGateRefusedError as n, measureJudgeAgreement as o, snapshotFromAgreement as p, addCalibrationVerdicts as r, registerCalibrationSet as s, DEFAULT_JUDGE_GATE_POLICY as t, fileSentinelStore as u };
637
+
638
+ //# sourceMappingURL=judge-gate-C59znGmR.js.map