@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 @@
1
+ {"version":3,"file":"paired-binary-Q36bhn2R.js","names":[],"sources":["../src/statistics/paired-binary.ts"],"sourcesContent":["import { regularizedIncompleteBeta } from '../math/special-functions'\nimport { binomialSignTwoSided, zQuantile } from './internal'\n\n// ── Binomial proportion + paired-binary + coding-eval estimators ─────\n//\n// The paired family above (pairedBootstrap/pairedTTest/wilcoxonSignedRank)\n// operates on continuous scores. Pass/fail A/B comparisons — \"does treatment\n// X raise the success RATE vs control\" — are binary and paired, so they need\n// their own correct estimators: McNemar for significance (only the discordant\n// pairs carry signal), the paired risk difference for effect size, Wilson for\n// a single-arm proportion CI, and pass@k for the standard k-sample coding-eval\n// metric. The normal approximation is wrong for proportions near 0/1 and for\n// the small discordant counts typical of eval runs, so these are exact /\n// Wilson-based, not Wald.\n\n/** A binomial proportion estimate with a confidence interval. */\nexport interface ProportionInterval {\n /** Point estimate successes / n; 0 is a compatibility placeholder when n = 0. */\n estimate: number\n /** Lower bound, clamped to [0, 1]. */\n lower: number\n /** Upper bound, clamped to [0, 1]. */\n upper: number\n}\n\n/**\n * Wilson score interval for a binomial proportion. Correct at small n and near\n * 0/1, where the normal (Wald) approximation produces bounds outside [0, 1] and\n * understates coverage. Use this for any pass-rate / hit-rate / realness-rate\n * CI — the continuous `confidenceInterval` assumes the wrong distribution for a\n * proportion. Counts must be finite nonnegative integers, successes cannot exceed\n * n, and confidence must lie strictly between zero and one. With n = 0 the\n * interval is the full [0, 1] support: no rate is ruled out. The estimate stays\n * 0 for compatibility, not as a measured failure rate; retain n alongside it.\n */\nexport function wilson(successes: number, n: number, confidence = 0.95): ProportionInterval {\n if (!Number.isSafeInteger(n) || n < 0) {\n throw new Error('wilson: n must be a finite nonnegative integer')\n }\n if (!Number.isSafeInteger(successes) || successes < 0 || successes > n) {\n throw new Error(`wilson: successes (${successes}) must be in [0, ${n}]`)\n }\n if (!Number.isFinite(confidence) || confidence <= 0 || confidence >= 1) {\n throw new Error('wilson: confidence must be between zero and one')\n }\n if (n === 0) return { estimate: 0, lower: 0, upper: 1 }\n const z = zQuantile(1 - (1 - confidence) / 2)\n const p = successes / n\n const z2 = z * z\n const denom = 1 + z2 / n\n const center = (p + z2 / (2 * n)) / denom\n const half = (z * Math.sqrt((p * (1 - p) + z2 / (4 * n)) / n)) / denom\n return {\n estimate: p,\n lower: Math.max(0, center - half),\n upper: Math.min(1, center + half),\n }\n}\n\n/**\n * Are these per-item outcomes binary (every value exactly 0 or 1)?\n *\n * The discriminator a promotion gate needs before choosing a paired statistic.\n * On binary outcomes the paired delta vector lives in {-1, 0, +1} and is\n * normally dominated by zeros (both arms solve, or both arms miss, most items),\n * so its MEDIAN is pinned at exactly 0 no matter how large the real shift in\n * success rate is — and a bootstrap CI on that median collapses to [0, 0].\n * A gate keying on `ci.low > threshold` is then structurally unable to see\n * either a gain or a regression. Detect this shape and switch to the\n * paired-binary estimators ({@link mcnemar}, {@link pairedRiskDifference})\n * instead of silently answering \"no\" forever.\n *\n * Empty input is NOT binary: there is no evidence of the outcome's shape, and\n * defaulting an empty vector into the binary branch would pick a statistic on\n * no data at all.\n *\n * NOT the right discriminator for a gate. It recognises the literal {0, 1}\n * encoding and nothing else, so a pass/fail dimension emitted on 0-100 — which\n * judges in this codebase do routinely — reads as non-binary, and a single\n * partial-credit score in an otherwise pass/fail vector flips it to false while\n * leaving the median just as blind. Gates want {@link pairedBinaryScale} (any\n * two-point encoding). This predicate remains for callers that specifically\n * mean \"literally 0/1\".\n */\nexport function isBinaryOutcomeVector(values: ArrayLike<number>): boolean {\n if (values.length === 0) return false\n for (let i = 0; i < values.length; i++) {\n const v = values[i]!\n if (v !== 0 && v !== 1) return false\n }\n return true\n}\n\n/** Result of a McNemar paired-binary significance test. */\nexport interface McNemarResult {\n /** Total paired observations. */\n n: number\n /** Discordant pairs (b + c) — the only ones that carry signal. */\n nDiscordant: number\n /** Pairs where treatment succeeded and control failed (\"newly correct\"). */\n b: number\n /** Pairs where control succeeded and treatment failed (\"newly wrong\"). */\n c: number\n /** Continuity-corrected chi-square statistic (reference; exact p drives the call). */\n statistic: number\n /** Two-sided p-value. Exact (binomial sign test on discordant pairs). */\n pValue: number\n}\n\n/**\n * McNemar's test for paired binary outcomes — the correct significance test for\n * \"does treatment change the success rate vs control on the SAME items\". Only\n * discordant pairs (one arm right, the other wrong) carry information; concordant\n * pairs are uninformative, so a paired t-test / two-proportion z-test on the raw\n * rates is wrong here. The p-value is exact: under H0 the b \"treatment-wins\" are\n * Binomial(b + c, 0.5), so the two-sided p is the doubled binomial tail — correct\n * at the small discordant counts typical of eval runs (no continuity-corrected\n * chi-square approximation needed, though it is returned as `statistic` for\n * reference). Inputs are paired 0/1 (or boolean) arrays, control first to match\n * the module's (before, after) convention. Throws on unequal lengths.\n */\nexport function mcnemar(\n control: ArrayLike<number | boolean>,\n treatment: ArrayLike<number | boolean>,\n): McNemarResult {\n if (control.length !== treatment.length) {\n throw new Error(`mcnemar: unequal sample sizes (${control.length} vs ${treatment.length})`)\n }\n const n = control.length\n let b = 0 // treatment 1, control 0\n let c = 0 // treatment 0, control 1\n for (let i = 0; i < n; i++) {\n const ctrl = control[i] ? 1 : 0\n const treat = treatment[i] ? 1 : 0\n if (treat === 1 && ctrl === 0) b++\n else if (treat === 0 && ctrl === 1) c++\n }\n const nDiscordant = b + c\n const statistic = nDiscordant === 0 ? 0 : (Math.abs(b - c) - 1) ** 2 / nDiscordant\n return { n, nDiscordant, b, c, statistic, pValue: binomialSignTwoSided(b, c) }\n}\n\n/** A paired binary effect size (treatment rate − control rate) with a CI. */\nexport interface RiskDifferenceResult {\n /** Total paired observations. */\n n: number\n /** Discordant pairs: treatment-win count. */\n b: number\n /** Discordant pairs: control-win count. */\n c: number\n /** Paired risk difference (b − c) / n; 0 is a placeholder when n = 0. */\n riskDifference: number\n /** Lower bound of the CI, clamped to [-1, 1]. */\n lower: number\n /** Upper bound of the CI, clamped to [-1, 1]. */\n upper: number\n /** Confidence level used. */\n confidence: number\n}\n\n/**\n * Paired risk difference (the effect-size companion to {@link mcnemar}): the\n * change in success rate p(treatment) − p(control) on matched items, which for\n * paired binary data equals (b − c) / n. The CI uses the paired variance from\n * the discordant counts, not the independent-samples formula (which overstates\n * the interval by ignoring the pairing). Inputs are paired 0/1 (or boolean)\n * arrays, control first. Throws on unequal lengths. With no pairs, returns the\n * full [-1, 1] support and a compatibility point estimate of 0, not equivalence.\n *\n * REPORTING ONLY — do NOT decide a promotion on this interval. The CI is a Wald\n * normal approximation, which badly UNDERCOVERS when only a handful of pairs are\n * discordant: at n = 3 with b = 2, c = 0 it returns [0.133, 1.000], excluding 0,\n * while McNemar's exact test on the same data gives p = 0.50. A gate keying on\n * `lower > 0` would promote noise. Use {@link pairedRiskDifferenceExact}, whose\n * interval is dual to the exact test by construction, for any decision.\n */\nexport function pairedRiskDifference(\n control: ArrayLike<number | boolean>,\n treatment: ArrayLike<number | boolean>,\n confidence = 0.95,\n): RiskDifferenceResult {\n if (control.length !== treatment.length) {\n throw new Error(\n `pairedRiskDifference: unequal sample sizes (${control.length} vs ${treatment.length})`,\n )\n }\n const n = control.length\n if (n === 0) return { n: 0, b: 0, c: 0, riskDifference: 0, lower: -1, upper: 1, confidence }\n let b = 0\n let c = 0\n for (let i = 0; i < n; i++) {\n const ctrl = control[i] ? 1 : 0\n const treat = treatment[i] ? 1 : 0\n if (treat === 1 && ctrl === 0) b++\n else if (treat === 0 && ctrl === 1) c++\n }\n const rd = (b - c) / n\n const variance = (b + c - (b - c) ** 2 / n) / (n * n)\n const z = zQuantile(1 - (1 - confidence) / 2)\n const half = z * Math.sqrt(Math.max(0, variance))\n return {\n n,\n b,\n c,\n riskDifference: rd,\n lower: Math.max(-1, rd - half),\n upper: Math.min(1, rd + half),\n confidence,\n }\n}\n\n/** A paired binary effect size with an EXACT interval and the exact test that\n * bounds it — one object so a caller cannot read the estimate without the\n * significance it is entitled to. */\nexport interface ExactRiskDifferenceResult {\n /** Total paired observations. */\n n: number\n /** Discordant pairs: treatment-win count. */\n b: number\n /** Discordant pairs: control-win count. */\n c: number\n /** Discordant pairs (b + c) — the only ones carrying information. */\n nDiscordant: number\n /** Paired risk difference (b − c) / n; 0 is a placeholder when n = 0. */\n riskDifference: number\n /** Exact conditional CI lower bound: -1 with no pairs; 0 with only concordant pairs. */\n lower: number\n /** Exact conditional CI upper bound: 1 with no pairs; 0 with only concordant pairs. */\n upper: number\n /** Confidence level used. */\n confidence: number\n /** McNemar's exact two-sided p-value on the same discordant counts. */\n pValue: number\n}\n\n/**\n * Paired risk difference with the EXACT CONDITIONAL interval — the estimator a\n * promotion gate may decide on.\n *\n * Conditional on the number of discordant pairs m = b + c, the treatment-win\n * count b is Binomial(m, π) with π = P(treatment wins | discordant), and the\n * risk difference is an exact reparameterisation: RD = (2π − 1)·m/n. So a\n * Clopper-Pearson exact interval for π maps straight onto RD. This buys the\n * property the Wald interval in {@link pairedRiskDifference} does not have:\n *\n * **`lower > 0` ⟺ McNemar's exact test rejects at α = 1 − confidence.**\n *\n * Clopper-Pearson excludes π = 0.5 exactly when the two-sided exact binomial\n * test of π = 0.5 rejects, and that test IS {@link mcnemar}'s p-value — so the\n * interval and the test can never disagree, and a gate keyed on `lower` cannot\n * promote what the exact test refuses. The exact p is returned in the same\n * object so the two are impossible to compute apart.\n *\n * The interval is conservative (exact intervals over-cover; conditioning on m\n * discards the concordant pairs' information about m itself). That is the\n * correct direction for a promotion gate: it refuses more often, never less.\n *\n * With n = 0, returns the full [-1, 1] support, p = 1, and a compatibility\n * point estimate of 0. With n > 0 and m = 0, π is not identified: the result\n * is the degenerate [0, 0] with p = 1. That is NOT evidence of equivalence —\n * callers must treat a zero-width interval as \"cannot decide\", not as \"no\n * difference\". Inputs are paired 0/1 (or boolean) arrays, control first.\n * Throws on unequal lengths.\n */\nexport function pairedRiskDifferenceExact(\n control: ArrayLike<number | boolean>,\n treatment: ArrayLike<number | boolean>,\n confidence = 0.95,\n): ExactRiskDifferenceResult {\n if (control.length !== treatment.length) {\n throw new Error(\n `pairedRiskDifferenceExact: unequal sample sizes (${control.length} vs ${treatment.length})`,\n )\n }\n if (confidence <= 0 || confidence >= 1) {\n throw new Error(`pairedRiskDifferenceExact: confidence must be in (0,1), got ${confidence}`)\n }\n const n = control.length\n if (n === 0) {\n return {\n n: 0,\n b: 0,\n c: 0,\n nDiscordant: 0,\n riskDifference: 0,\n lower: -1,\n upper: 1,\n confidence,\n pValue: 1,\n }\n }\n let b = 0\n let c = 0\n for (let i = 0; i < n; i++) {\n const ctrl = control[i] ? 1 : 0\n const treat = treatment[i] ? 1 : 0\n if (treat === 1 && ctrl === 0) b++\n else if (treat === 0 && ctrl === 1) c++\n }\n const m = b + c\n const riskDifference = (b - c) / n\n const pValue = binomialSignTwoSided(b, c)\n if (m === 0) {\n return { n, b, c, nDiscordant: 0, riskDifference: 0, lower: 0, upper: 0, confidence, pValue }\n }\n const alpha = 1 - confidence\n const piLow = b === 0 ? 0 : betaQuantile(alpha / 2, b, m - b + 1)\n const piHigh = b === m ? 1 : betaQuantile(1 - alpha / 2, b + 1, m - b)\n const scale = m / n\n return {\n n,\n b,\n c,\n nDiscordant: m,\n riskDifference,\n lower: Math.max(-1, (2 * piLow - 1) * scale),\n upper: Math.min(1, (2 * piHigh - 1) * scale),\n confidence,\n pValue,\n }\n}\n\n/** Inverse regularized incomplete beta by bisection on\n * {@link regularizedIncompleteBeta}, which is monotone increasing in x. 80\n * halvings of [0,1] resolve to ~8e-25, far past the continued fraction's own\n * 3e-15 tolerance, so the quantile is as exact as the CDF it inverts. */\nfunction betaQuantile(p: number, a: number, b: number): number {\n if (p <= 0) return 0\n if (p >= 1) return 1\n let lo = 0\n let hi = 1\n for (let i = 0; i < 80; i++) {\n const mid = (lo + hi) / 2\n if (regularizedIncompleteBeta(mid, a, b) < p) lo = mid\n else hi = mid\n }\n return (lo + hi) / 2\n}\n\n/** A paired binary effect size with an interval that is valid at a NONZERO\n * margin — the estimator a noninferiority decision may be made on. */\nexport interface ScoreRiskDifferenceResult {\n /** Total paired observations. */\n n: number\n /** Discordant pairs: treatment-win count. */\n b: number\n /** Discordant pairs: control-win count. */\n c: number\n /** Discordant pairs (b + c). */\n nDiscordant: number\n /** Paired risk difference (b − c) / n; 0 is a placeholder when n = 0. */\n riskDifference: number\n /** Score-interval lower bound on the population risk difference. */\n lower: number\n /** Score-interval upper bound on the population risk difference. */\n upper: number\n /** Confidence level used. */\n confidence: number\n}\n\n/**\n * Constrained MLE of q = P(treatment loses) under the hypothesis RD = `delta`.\n *\n * Profiling the two concordant cells out of the multinomial leaves\n * `L(q) = b·log(q+delta) + c·log(q) + e·log(1 − 2q − delta)` with `e = n − b − c`,\n * whose stationary point is the positive root of\n * `2n·q² − [(b + c) − delta·(b + 3c + 2e)]·q − c·delta·(1 − delta) = 0`.\n * At `delta = 0` this returns `(b + c) / 2n`, the familiar null.\n */\nfunction constrainedLossRate(b: number, c: number, n: number, delta: number): number {\n const e = n - b - c\n const quadratic = 2 * n\n const linear = -(b + c - delta * (b + 3 * c + 2 * e))\n const constant = -c * delta * (1 - delta)\n const discriminant = linear * linear - 4 * quadratic * constant\n const root = discriminant > 0 ? Math.sqrt(discriminant) : 0\n const q = (-linear + root) / (2 * quadratic)\n // Clamp into the region where all four cell probabilities stay non-negative.\n return Math.min(Math.max(q, Math.max(0, -delta)), Math.max(0, (1 - delta) / 2))\n}\n\n/** Tango's score statistic for H0: RD = `delta`. `Var(b − c) = n·(2q + delta −\n * delta²)` under that hypothesis, evaluated at the constrained MLE of q. */\nfunction tangoScore(b: number, c: number, n: number, delta: number): number {\n const numerator = b - c - n * delta\n const q = constrainedLossRate(b, c, n, delta)\n const variance = n * (2 * q + delta * (1 - delta))\n if (!(variance > 0)) {\n if (numerator === 0) return 0\n return numerator > 0 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY\n }\n return numerator / Math.sqrt(variance)\n}\n\n/**\n * Paired risk difference with TANGO'S (1998) SCORE INTERVAL — the estimator a\n * promotion gate may decide on **at a nonzero margin**.\n *\n * {@link pairedRiskDifferenceExact} conditions on the observed discordant count\n * `m = b + c`, builds a Clopper-Pearson interval for the win share among those\n * `m` pairs, and multiplies by the observed `m/n`. That is exact for testing\n * RD = 0 — it is dual to McNemar — but it is NOT a confidence interval for the\n * population risk difference at a nonzero margin, because the sampling\n * variability of `m/n` itself is discarded. The gap is not academic: with the\n * production caller's `pairedDeltaThreshold: -0.05`, a process whose true risk\n * difference sits exactly on that margin clears a nominal-95 % `lower > margin`\n * check 24.75 % of the time at n = 40 and 43.95 % at n = 76 (2000 replicates\n * each) when the conditional interval decides.\n *\n * Tango's interval inverts the score test of RD = delta, which estimates the\n * nuisance loss rate under each hypothesised delta instead of fixing it at the\n * observed value, so `m` contributes its own uncertainty. It is the method\n * `ratesci::scorepairci` uses for paired risk-difference noninferiority, and it\n * is not conditional, so it stays valid as the margin moves away from zero.\n *\n * The bounds are found by bisecting `tangoScore(delta) = ±z` — the score is\n * monotone decreasing in delta, so each crossing is unique. Inputs are paired\n * 0/1 (or boolean) arrays, control first. Throws on unequal lengths.\n */\nexport function pairedRiskDifferenceScore(\n control: ArrayLike<number | boolean>,\n treatment: ArrayLike<number | boolean>,\n confidence = 0.95,\n): ScoreRiskDifferenceResult {\n if (control.length !== treatment.length) {\n throw new Error(\n `pairedRiskDifferenceScore: unequal sample sizes (${control.length} vs ${treatment.length})`,\n )\n }\n if (confidence <= 0 || confidence >= 1) {\n throw new Error(`pairedRiskDifferenceScore: confidence must be in (0,1), got ${confidence}`)\n }\n const n = control.length\n if (n === 0) {\n return { n: 0, b: 0, c: 0, nDiscordant: 0, riskDifference: 0, lower: -1, upper: 1, confidence }\n }\n let b = 0\n let c = 0\n for (let i = 0; i < n; i++) {\n const ctrl = control[i] ? 1 : 0\n const treat = treatment[i] ? 1 : 0\n if (treat === 1 && ctrl === 0) b++\n else if (treat === 0 && ctrl === 1) c++\n }\n const riskDifference = (b - c) / n\n const z = zQuantile(1 - (1 - confidence) / 2)\n\n // Lower bound: the smallest delta still inside the interval, i.e. the root of\n // score(delta) = +z on [-1, riskDifference]. score(riskDifference) = 0 < z, so\n // the right endpoint is always inside and the bisection is well posed.\n let lo = -1\n let hi = riskDifference\n // 64 halvings resolve the [-1, 1] search interval below 6e-20, past the\n // precision available to the score calculation. More iterations only\n // repeat identical floating-point values while multiplying every gate\n // decision's cost.\n for (let i = 0; i < 64; i++) {\n const mid = (lo + hi) / 2\n if (tangoScore(b, c, n, mid) > z) lo = mid\n else hi = mid\n }\n const lower = (lo + hi) / 2\n\n // Upper bound: root of score(delta) = -z on [riskDifference, 1].\n let ulo = riskDifference\n let uhi = 1\n for (let i = 0; i < 64; i++) {\n const mid = (ulo + uhi) / 2\n if (tangoScore(b, c, n, mid) > -z) ulo = mid\n else uhi = mid\n }\n const upper = (ulo + uhi) / 2\n\n return {\n n,\n b,\n c,\n nDiscordant: b + c,\n riskDifference,\n lower: Math.max(-1, lower),\n upper: Math.min(1, upper),\n confidence,\n }\n}\n\n/**\n * The common positive level `s` such that EVERY value across both paired arms is\n * exactly 0 or `s` — i.e. the outcome is pass/fail, whatever encoding it arrived\n * in. Returns null when the outcomes are not two-point, when the two arms use\n * different levels, or when no positive value was observed at all (all-zero\n * arms: the level is not identified, and there is nothing to decide anyway).\n *\n * This is the scale-aware successor to {@link isBinaryOutcomeVector}, which only\n * recognises literal {0, 1}. Judges in this codebase emit dimensions on 0-100 as\n * well as [0,1] (see `detectScale` in `campaign/gates/statistical-heldout.ts`),\n * so a pass/fail dimension routinely arrives as {0, 100} and a {0,1}-only test\n * silently sends it down the median path that cannot see it. Any positive level\n * is accepted, not just 1 and 100: for a two-point {0, s} outcome the mean paired\n * delta is exactly s·(b − c)/n, so the binary estimators apply after dividing by\n * s and rescaling the result back into the caller's native units.\n *\n * Non-finite values ⇒ null: an unusable outcome must not be classified as a\n * clean pass/fail shape.\n */\nexport function pairedBinaryScale(\n before: ArrayLike<number>,\n after: ArrayLike<number>,\n): number | null {\n let level: number | null = null\n for (const arm of [before, after]) {\n for (let i = 0; i < arm.length; i++) {\n const v = arm[i]!\n if (!Number.isFinite(v)) return null\n if (v === 0) continue\n if (v < 0) return null\n if (level === null) level = v\n else if (v !== level) return null\n }\n }\n return level\n}\n\n/**\n * Unbiased pass@k for code generation (Chen et al. 2021, \"Evaluating Large\n * Language Models Trained on Code\"). Given `n` independent samples for one\n * problem of which `c` pass, the probability that at least one of a random k of\n * them passes is 1 − C(n−c, k) / C(n, k). Estimating pass@k as \"did any of the\n * first k pass\" is biased high at small n; this is the variance-reduced estimator\n * averaged implicitly over all k-subsets. Average the per-problem values across\n * the suite for the corpus pass@k. Computed in the numerically stable product\n * form. Requires 1 ≤ k ≤ n and 0 ≤ c ≤ n.\n */\nexport function passAtK(n: number, c: number, k: number): number {\n if (!Number.isInteger(n) || !Number.isInteger(c) || !Number.isInteger(k)) {\n throw new Error(`passAtK: n, c, k must be integers (got n=${n}, c=${c}, k=${k})`)\n }\n if (k < 1 || k > n || c < 0 || c > n) {\n throw new Error(`passAtK: require 1 ≤ k ≤ n and 0 ≤ c ≤ n (got n=${n}, c=${c}, k=${k})`)\n }\n if (n - c < k) return 1\n let prob = 1\n for (let i = n - c + 1; i <= n; i++) prob *= 1 - k / i\n return 1 - prob\n}\n"],"mappings":";;;;;;;;;;;;AAmCA,SAAgB,OAAO,WAAmB,GAAW,aAAa,KAA0B;CAC1F,IAAI,CAAC,OAAO,cAAc,CAAC,KAAK,IAAI,GAClC,MAAM,IAAI,MAAM,gDAAgD;CAElE,IAAI,CAAC,OAAO,cAAc,SAAS,KAAK,YAAY,KAAK,YAAY,GACnE,MAAM,IAAI,MAAM,sBAAsB,UAAU,mBAAmB,EAAE,EAAE;CAEzE,IAAI,CAAC,OAAO,SAAS,UAAU,KAAK,cAAc,KAAK,cAAc,GACnE,MAAM,IAAI,MAAM,iDAAiD;CAEnE,IAAI,MAAM,GAAG,OAAO;EAAE,UAAU;EAAG,OAAO;EAAG,OAAO;CAAE;CACtD,MAAM,IAAI,UAAU,KAAK,IAAI,cAAc,CAAC;CAC5C,MAAM,IAAI,YAAY;CACtB,MAAM,KAAK,IAAI;CACf,MAAM,QAAQ,IAAI,KAAK;CACvB,MAAM,UAAU,IAAI,MAAM,IAAI,MAAM;CACpC,MAAM,OAAQ,IAAI,KAAK,MAAM,KAAK,IAAI,KAAK,MAAM,IAAI,MAAM,CAAC,IAAK;CACjE,OAAO;EACL,UAAU;EACV,OAAO,KAAK,IAAI,GAAG,SAAS,IAAI;EAChC,OAAO,KAAK,IAAI,GAAG,SAAS,IAAI;CAClC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,sBAAsB,QAAoC;CACxE,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,IAAI,OAAO;EACjB,IAAI,MAAM,KAAK,MAAM,GAAG,OAAO;CACjC;CACA,OAAO;AACT;;;;;;;;;;;;;AA8BA,SAAgB,QACd,SACA,WACe;CACf,IAAI,QAAQ,WAAW,UAAU,QAC/B,MAAM,IAAI,MAAM,kCAAkC,QAAQ,OAAO,MAAM,UAAU,OAAO,EAAE;CAE5F,MAAM,IAAI,QAAQ;CAClB,IAAI,IAAI;CACR,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,OAAO,QAAQ,KAAK,IAAI;EAC9B,MAAM,QAAQ,UAAU,KAAK,IAAI;EACjC,IAAI,UAAU,KAAK,SAAS,GAAG;OAC1B,IAAI,UAAU,KAAK,SAAS,GAAG;CACtC;CACA,MAAM,cAAc,IAAI;CACxB,MAAM,YAAY,gBAAgB,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,IAAI,MAAM,IAAI;CACvE,OAAO;EAAE;EAAG;EAAa;EAAG;EAAG;EAAW,QAAQ,qBAAqB,GAAG,CAAC;CAAE;AAC/E;;;;;;;;;;;;;;;;;AAoCA,SAAgB,qBACd,SACA,WACA,aAAa,KACS;CACtB,IAAI,QAAQ,WAAW,UAAU,QAC/B,MAAM,IAAI,MACR,+CAA+C,QAAQ,OAAO,MAAM,UAAU,OAAO,EACvF;CAEF,MAAM,IAAI,QAAQ;CAClB,IAAI,MAAM,GAAG,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,GAAG;EAAG,gBAAgB;EAAG,OAAO;EAAI,OAAO;EAAG;CAAW;CAC3F,IAAI,IAAI;CACR,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,OAAO,QAAQ,KAAK,IAAI;EAC9B,MAAM,QAAQ,UAAU,KAAK,IAAI;EACjC,IAAI,UAAU,KAAK,SAAS,GAAG;OAC1B,IAAI,UAAU,KAAK,SAAS,GAAG;CACtC;CACA,MAAM,MAAM,IAAI,KAAK;CACrB,MAAM,YAAY,IAAI,KAAK,IAAI,MAAM,IAAI,MAAM,IAAI;CAEnD,MAAM,OADI,UAAU,KAAK,IAAI,cAAc,CAC9B,IAAI,KAAK,KAAK,KAAK,IAAI,GAAG,QAAQ,CAAC;CAChD,OAAO;EACL;EACA;EACA;EACA,gBAAgB;EAChB,OAAO,KAAK,IAAI,IAAI,KAAK,IAAI;EAC7B,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI;EAC5B;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuDA,SAAgB,0BACd,SACA,WACA,aAAa,KACc;CAC3B,IAAI,QAAQ,WAAW,UAAU,QAC/B,MAAM,IAAI,MACR,oDAAoD,QAAQ,OAAO,MAAM,UAAU,OAAO,EAC5F;CAEF,IAAI,cAAc,KAAK,cAAc,GACnC,MAAM,IAAI,MAAM,+DAA+D,YAAY;CAE7F,MAAM,IAAI,QAAQ;CAClB,IAAI,MAAM,GACR,OAAO;EACL,GAAG;EACH,GAAG;EACH,GAAG;EACH,aAAa;EACb,gBAAgB;EAChB,OAAO;EACP,OAAO;EACP;EACA,QAAQ;CACV;CAEF,IAAI,IAAI;CACR,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,OAAO,QAAQ,KAAK,IAAI;EAC9B,MAAM,QAAQ,UAAU,KAAK,IAAI;EACjC,IAAI,UAAU,KAAK,SAAS,GAAG;OAC1B,IAAI,UAAU,KAAK,SAAS,GAAG;CACtC;CACA,MAAM,IAAI,IAAI;CACd,MAAM,kBAAkB,IAAI,KAAK;CACjC,MAAM,SAAS,qBAAqB,GAAG,CAAC;CACxC,IAAI,MAAM,GACR,OAAO;EAAE;EAAG;EAAG;EAAG,aAAa;EAAG,gBAAgB;EAAG,OAAO;EAAG,OAAO;EAAG;EAAY;CAAO;CAE9F,MAAM,QAAQ,IAAI;CAClB,MAAM,QAAQ,MAAM,IAAI,IAAI,aAAa,QAAQ,GAAG,GAAG,IAAI,IAAI,CAAC;CAChE,MAAM,SAAS,MAAM,IAAI,IAAI,aAAa,IAAI,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC;CACrE,MAAM,QAAQ,IAAI;CAClB,OAAO;EACL;EACA;EACA;EACA,aAAa;EACb;EACA,OAAO,KAAK,IAAI,KAAK,IAAI,QAAQ,KAAK,KAAK;EAC3C,OAAO,KAAK,IAAI,IAAI,IAAI,SAAS,KAAK,KAAK;EAC3C;EACA;CACF;AACF;;;;;AAMA,SAAS,aAAa,GAAW,GAAW,GAAmB;CAC7D,IAAI,KAAK,GAAG,OAAO;CACnB,IAAI,KAAK,GAAG,OAAO;CACnB,IAAI,KAAK;CACT,IAAI,KAAK;CACT,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,OAAO,KAAK,MAAM;EACxB,IAAI,0BAA0B,KAAK,GAAG,CAAC,IAAI,GAAG,KAAK;OAC9C,KAAK;CACZ;CACA,QAAQ,KAAK,MAAM;AACrB;;;;;;;;;;AAgCA,SAAS,oBAAoB,GAAW,GAAW,GAAW,OAAuB;CACnF,MAAM,IAAI,IAAI,IAAI;CAClB,MAAM,YAAY,IAAI;CACtB,MAAM,SAAS,EAAE,IAAI,IAAI,SAAS,IAAI,IAAI,IAAI,IAAI;CAClD,MAAM,WAAW,CAAC,IAAI,SAAS,IAAI;CACnC,MAAM,eAAe,SAAS,SAAS,IAAI,YAAY;CACvD,MAAM,OAAO,eAAe,IAAI,KAAK,KAAK,YAAY,IAAI;CAC1D,MAAM,KAAK,CAAC,SAAS,SAAS,IAAI;CAElC,OAAO,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,KAAK,IAAI,IAAI,IAAI,SAAS,CAAC,CAAC;AAChF;;;AAIA,SAAS,WAAW,GAAW,GAAW,GAAW,OAAuB;CAC1E,MAAM,YAAY,IAAI,IAAI,IAAI;CAE9B,MAAM,WAAW,KAAK,IADZ,oBAAoB,GAAG,GAAG,GAAG,KACb,IAAI,SAAS,IAAI;CAC3C,IAAI,EAAE,WAAW,IAAI;EACnB,IAAI,cAAc,GAAG,OAAO;EAC5B,OAAO,YAAY,IAAI,OAAO,oBAAoB,OAAO;CAC3D;CACA,OAAO,YAAY,KAAK,KAAK,QAAQ;AACvC;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,0BACd,SACA,WACA,aAAa,KACc;CAC3B,IAAI,QAAQ,WAAW,UAAU,QAC/B,MAAM,IAAI,MACR,oDAAoD,QAAQ,OAAO,MAAM,UAAU,OAAO,EAC5F;CAEF,IAAI,cAAc,KAAK,cAAc,GACnC,MAAM,IAAI,MAAM,+DAA+D,YAAY;CAE7F,MAAM,IAAI,QAAQ;CAClB,IAAI,MAAM,GACR,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,GAAG;EAAG,aAAa;EAAG,gBAAgB;EAAG,OAAO;EAAI,OAAO;EAAG;CAAW;CAEhG,IAAI,IAAI;CACR,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,GAAG,KAAK;EAC1B,MAAM,OAAO,QAAQ,KAAK,IAAI;EAC9B,MAAM,QAAQ,UAAU,KAAK,IAAI;EACjC,IAAI,UAAU,KAAK,SAAS,GAAG;OAC1B,IAAI,UAAU,KAAK,SAAS,GAAG;CACtC;CACA,MAAM,kBAAkB,IAAI,KAAK;CACjC,MAAM,IAAI,UAAU,KAAK,IAAI,cAAc,CAAC;CAK5C,IAAI,KAAK;CACT,IAAI,KAAK;CAKT,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,OAAO,KAAK,MAAM;EACxB,IAAI,WAAW,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK;OAClC,KAAK;CACZ;CACA,MAAM,SAAS,KAAK,MAAM;CAG1B,IAAI,MAAM;CACV,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK;EAC3B,MAAM,OAAO,MAAM,OAAO;EAC1B,IAAI,WAAW,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,MAAM;OACpC,MAAM;CACb;CACA,MAAM,SAAS,MAAM,OAAO;CAE5B,OAAO;EACL;EACA;EACA;EACA,aAAa,IAAI;EACjB;EACA,OAAO,KAAK,IAAI,IAAI,KAAK;EACzB,OAAO,KAAK,IAAI,GAAG,KAAK;EACxB;CACF;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,kBACd,QACA,OACe;CACf,IAAI,QAAuB;CAC3B,KAAK,MAAM,OAAO,CAAC,QAAQ,KAAK,GAC9B,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,MAAM,IAAI,IAAI;EACd,IAAI,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO;EAChC,IAAI,MAAM,GAAG;EACb,IAAI,IAAI,GAAG,OAAO;EAClB,IAAI,UAAU,MAAM,QAAQ;OACvB,IAAI,MAAM,OAAO,OAAO;CAC/B;CAEF,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,QAAQ,GAAW,GAAW,GAAmB;CAC/D,IAAI,CAAC,OAAO,UAAU,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,GACrE,MAAM,IAAI,MAAM,4CAA4C,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;CAElF,IAAI,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,GACjC,MAAM,IAAI,MAAM,mDAAmD,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;CAEzF,IAAI,IAAI,IAAI,GAAG,OAAO;CACtB,IAAI,OAAO;CACX,KAAK,IAAI,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK,QAAQ,IAAI,IAAI;CACrD,OAAO,IAAI;AACb"}
@@ -1,4 +1,4 @@
1
- import { a as pairedRiskDifferenceExact, o as pairedRiskDifferenceScore, r as pairedBinaryScale } from "./paired-binary-b2L3WKqr.js";
1
+ import { a as pairedRiskDifferenceExact, o as pairedRiskDifferenceScore, r as pairedBinaryScale } from "./paired-binary-Q36bhn2R.js";
2
2
  import { a as pairedSignTest, i as pairedDeltaTieFraction, r as pairedBootstrap } from "./paired-tests-jbwPdSz5.js";
3
3
  //#region src/paired-delta-test.ts
4
4
  /** Smallest all-positive sample that can clear a one-sided exact sign test. */
@@ -255,4 +255,4 @@ function fmt(x) {
255
255
  //#endregion
256
256
  export { pairedDeltaTest as i, pairedDecisionShape as n, minimumPairsForPairedDeltaTest as r, decidePairedPromotion as t };
257
257
 
258
- //# sourceMappingURL=paired-promotion-decision-2deJ7sDX.js.map
258
+ //# sourceMappingURL=paired-promotion-decision-BN6IQTiu.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paired-promotion-decision-2deJ7sDX.js","names":[],"sources":["../src/paired-delta-test.ts","../src/paired-promotion-decision.ts"],"sourcesContent":["import {\n type PairedBootstrapOptions,\n type PairedBootstrapResult,\n pairedBootstrap,\n pairedSignTest,\n} from './statistics'\n\nexport interface PairedDeltaTestOptions extends PairedBootstrapOptions {\n /** Smallest candidate-minus-baseline delta that counts as improvement. Default 0. */\n threshold?: number\n /** Caller-required paired observations. The exact test may impose a higher minimum. */\n minPairs?: number\n}\n\nexport interface PairedDeltaTestResult {\n bootstrap: PairedBootstrapResult\n method: 'bootstrap-ci' | 'exact-sign'\n /** Exact one-sided p-value below the bootstrap minimum; otherwise null. */\n pValue: number | null\n /** Effective observation minimum after accounting for confidence. */\n minimumPairs: number\n sufficient: boolean\n /**\n * The bootstrap interval has zero width (or is non-finite), so it carries no\n * information about how far the estimate could be wrong and cannot support a\n * decision in either direction. See {@link pairedDeltaTest}.\n */\n indeterminate: boolean\n significant: boolean\n}\n\n/** Smallest all-positive sample that can clear a one-sided exact sign test. */\nexport function minimumPairsForPairedDeltaTest(confidence = 0.95): number {\n if (!Number.isFinite(confidence) || confidence <= 0 || confidence >= 1) {\n throw new Error(\n `minimumPairsForPairedDeltaTest: confidence must be in (0,1), got ${confidence}`,\n )\n }\n const oneSidedAlpha = (1 - confidence) / 2\n return Math.ceil(Math.log2(1 / oneSidedAlpha))\n}\n\n/**\n * Tests whether a paired candidate-minus-baseline delta clears a threshold.\n *\n * At 20 or more pairs, the percentile bootstrap lower bound carries the\n * decision. Below that point the interval is descriptive only, so the function\n * switches to a pre-registered one-sided exact sign test. The exact path is\n * deliberately conservative: it requires both a point estimate above the\n * threshold and enough consistently positive paired differences.\n *\n * ## A zero-width interval is never significant\n *\n * When every paired delta is identical the resample distribution is a point\n * mass and the interval collapses: `[0, 0]` when all pairs tie, `[g, g]` on n\n * identical deltas of g. Neither says the effect is certain — both say the\n * sample carries no information about how far the estimate could be wrong, and\n * `low > threshold` then answers on the point estimate alone. It fails in both\n * directions: `[0, 0]` clears every NEGATIVE threshold, which is how a\n * tie-dominated pass/fail comparison laundered a regression into a\n * noninferiority pass, and `[g, g]` clears every threshold below g with no\n * spread behind it. Under a bounded asymmetric null whose true mean paired\n * delta is exactly 0 — 2 % of pairs dropping by 1.0, the rest gaining 0.0204 —\n * every sample that misses the drop is exactly that shape, and deciding on\n * `low > 0` promoted 65.65 % of samples at n = 20 against a nominal 5 %.\n *\n * So `indeterminate` is reported and `significant` is false whenever the\n * interval has zero width, on BOTH paths: at small n the exact sign test is a\n * test of the MEDIAN and a zero-spread sample is precisely where it stops\n * saying anything about the mean the caller is thresholding.\n *\n * `threshold` may be negative — that is a noninferiority margin, and it is the\n * regime the zero-width hole is worst in. For a two-point (pass/fail) outcome\n * the percentile bootstrap is not a valid interval at a nonzero margin at all;\n * use {@link decidePairedPromotion}, which routes those to Tango's score\n * interval, rather than thresholding this function's bootstrap directly.\n */\nexport function pairedDeltaTest(\n before: number[],\n after: number[],\n options: PairedDeltaTestOptions = {},\n): PairedDeltaTestResult {\n const threshold = options.threshold ?? 0\n if (!Number.isFinite(threshold)) {\n throw new Error(`pairedDeltaTest: threshold must be finite, got ${threshold}`)\n }\n const confidence = options.confidence ?? 0.95\n const exactMinimum = minimumPairsForPairedDeltaTest(confidence)\n const requestedMinimum = options.minPairs ?? exactMinimum\n if (!Number.isInteger(requestedMinimum) || requestedMinimum < 1) {\n throw new Error(`pairedDeltaTest: minPairs must be a positive integer, got ${requestedMinimum}`)\n }\n const minimumPairs = Math.max(requestedMinimum, exactMinimum)\n const bootstrap = pairedBootstrap(before, after, options)\n const sufficient = bootstrap.n >= minimumPairs\n // A point-mass resample distribution is an absence of evidence, not a\n // certainty, and it is the shape that clears every negative threshold. Both\n // paths refuse it: see the \"zero-width\" section of this function's docs.\n const indeterminate =\n bootstrap.n > 0 &&\n (!Number.isFinite(bootstrap.low) ||\n !Number.isFinite(bootstrap.high) ||\n bootstrap.low === bootstrap.high)\n\n if (bootstrap.gateEligible) {\n return {\n bootstrap,\n method: 'bootstrap-ci',\n pValue: null,\n minimumPairs,\n sufficient,\n indeterminate,\n significant: sufficient && !indeterminate && bootstrap.low > threshold,\n }\n }\n\n const differences = before.map((value, index) => after[index]! - value - threshold)\n const exact = pairedSignTest(differences, 'greater')\n const estimate = options.statistic === 'mean' ? bootstrap.mean : bootstrap.median\n return {\n bootstrap,\n method: 'exact-sign',\n pValue: exact.pValue,\n minimumPairs,\n sufficient,\n indeterminate,\n significant:\n sufficient &&\n !indeterminate &&\n estimate > threshold &&\n exact.pValue <= (1 - bootstrap.confidence) / 2,\n }\n}\n","/**\n * @module\n * ONE rule for \"does this paired interval clear a promotion threshold\".\n *\n * The rule below was derived on `HeldOutGate` (#479) after the same estimator\n * bug shipped twice. It then turned out that a SECOND gate — the composable\n * `heldOutGate`, plus everything else routed through `heldoutSignificance` —\n * still carried the original defect, because the rule had been written into one\n * gate's method body rather than into a shared function. Two copies of a\n * statistical rule is how a defect survives in one of them, so there is now\n * exactly one copy and both gates call it.\n *\n * Three things the rule does that a bare `pairedBootstrap(...).low > threshold`\n * does not:\n *\n * 1. **Two-point (pass/fail) outcomes decide on Tango's SCORE interval.** On a\n * pass/fail eval the paired delta vector is dominated by ties, so the\n * bootstrap of the mean is a resample of a lattice with three atoms and its\n * percentile interval is not valid at a nonzero margin. The score interval\n * (`pairedRiskDifferenceScore`) re-estimates the nuisance loss rate under\n * each hypothesised margin instead of fixing it at the observed value, which\n * is the only construction that stays a confidence interval as the margin\n * moves off zero — the regime every noninferiority threshold lives in.\n * Measured on the composable gate before this change, at a true risk\n * difference sitting exactly on the production caller's -0.05 margin and a\n * nominal 5 %: 14.60 % false promotion at n = 40 and 10.10 % at n = 76.\n * 2. **McNemar's exact test holds a VETO at every non-negative threshold.**\n * Redundant with the interval by construction and kept anyway, so that\n * swapping the estimator for one without that duality cannot silently\n * reintroduce \"promotes what the exact test refuses\". Witness: n = 6, b = 5,\n * c = 0 — no exact argument reaches alpha = 0.05 with 5 discordant pairs\n * (two-sided floor 2/2^5 = 0.0625), whatever an interval says. A NEGATIVE\n * threshold is a noninferiority question, which McNemar's test of \"no\n * difference\" is not the right test for, so the veto does not apply there.\n * 3. **A ZERO-WIDTH interval is refused, wherever it sits.** At [0, 0] it\n * cannot tell a gain from a regression and clears every negative threshold.\n * Away from zero it fails the opposite way: n identical positive deltas give\n * [g, g], which clears threshold 0 on no spread at all. Both are an absence\n * of evidence. Measured on the composable gate before this change, under a\n * bounded asymmetric null whose true mean paired delta is exactly 0: 88.50 %\n * false promotion at n = 6 and 65.65 % at n = 20 against a nominal 5 %.\n *\n * Eligibility follows the requested target. A continuous mean needs the\n * bootstrap minimum; binary outcomes and explicit median targets use the\n * exact-test minimum. A small-sample sign diagnostic cannot certify a mean\n * effect. These floors establish estimator eligibility, not adequate power.\n */\n\nimport { minimumPairsForPairedDeltaTest, pairedDeltaTest } from './paired-delta-test'\nimport {\n BOOTSTRAP_GATE_MIN_N,\n type PairedBootstrapResult,\n pairedBinaryScale,\n pairedDeltaTieFraction,\n pairedRiskDifferenceExact,\n pairedRiskDifferenceScore,\n} from './statistics'\n\n/** Which paired estimator produced the deciding interval. */\nexport type PairedDecisionStatistic =\n | 'paired_risk_difference'\n | 'mean_bootstrap'\n | 'median_bootstrap'\n\n/** Which test carried the decision, given the estimator and the sample size. */\nexport type PairedDecisionMethod = 'score-interval' | 'bootstrap-ci' | 'exact-sign'\n\n/** McNemar's exact paired-binary evidence, on the two-point path only. */\nexport interface PairedMcNemarEvidence {\n /** Discordant pairs the treatment won. */\n b: number\n /** Discordant pairs the control won. */\n c: number\n /** b + c — the only pairs carrying information. */\n nDiscordant: number\n /** Two-sided exact p-value. */\n pValue: number\n}\n\nexport interface PairedPromotionDecisionOptions {\n /** Smallest candidate-minus-baseline delta that counts as improvement, in the\n * caller's native units. May be negative (a noninferiority margin). Default 0. */\n threshold?: number\n /** Confidence level. Default 0.95. */\n confidence?: number\n /** Bootstrap resamples, on the paths where a bootstrap decides. Default 2000. */\n resamples?: number\n /** Deterministic bootstrap seed. Omitted ⇒ derived from the deltas. */\n seed?: number\n /** Caller-required paired observations. The test also imposes its own\n * minimum: bootstrap eligibility for a continuous mean, or the exact-test\n * minimum for binary and explicit median targets. This is not a power guarantee. */\n minPairs?: number\n /**\n * `'mean'` (default) routes by SHAPE: a two-point (pass/fail) outcome on any\n * encoding decides on the score interval, everything else on the mean\n * bootstrap. `'median'` forces the median bootstrap on every input, including\n * shapes where it is structurally blind — kept for callers who want outlier\n * robustness on genuinely continuous outcomes and accept that cost.\n */\n statistic?: 'mean' | 'median'\n /** Declared binary support {0, binaryScale}, including when all observations\n * are zero. Must be finite and positive; every observation must use that\n * support. Uses the risk-difference mean, so cannot accompany 'median'.\n * Omitted: infer binary support from observed positive values. */\n binaryScale?: number\n /**\n * Declare the outcome continuous: never infer a two-point support from the\n * observed values, so the estimator is fixed before the data is seen. A\n * sealed call on a continuous measure must route to the bootstrap whatever\n * the sample happens to look like; with inference on, a sample whose values\n * all land on {0, s} is silently decided on the score interval instead.\n * Measured under discovery's E1 alternative (deltas in {+1, -0.5, 0} on a\n * zero baseline) at 20 pairs: 5.4 % of 2,000 simulated samples (seed 11)\n * re-route without this flag, 0 with it; the exact rate is 5.54 %\n * (`paired-promotion-power.test.ts`). Cannot accompany `binaryScale`.\n */\n continuous?: boolean\n}\n\nexport interface PairedPromotionDecision {\n /** Paired observations supplied. */\n n: number\n /** Threshold the interval was judged against, native units. */\n threshold: number\n confidence: number\n statistic: PairedDecisionStatistic\n method: PairedDecisionMethod\n /** Declared or inferred positive level ({0,1} ⇒ 1, {0,100} ⇒ 100),\n * or null when the outcome is not two-point. Non-null is exactly the\n * condition for the `paired_risk_difference` path, and it is the factor\n * `delta` / `low` / `high` were rescaled by. */\n binaryScale: number | null\n /** Exact-tie fraction over the paired deltas; null when there are no pairs. */\n tieFraction: number | null\n /** Point estimate of the DECIDING statistic, in the caller's native units. */\n delta: number\n /** Lower bound of the DECIDING interval, native units. */\n low: number\n /** Upper bound of the DECIDING interval, native units. */\n high: number\n /** The bootstrap that decided, or null when the score interval did. Callers\n * that need a bootstrap as a diagnostic on the two-point path compute their\n * own — it is not computed here, so the binary path costs no resamples. */\n bootstrap: PairedBootstrapResult | null\n /** McNemar's exact evidence, or null off the two-point path. */\n mcnemar: PairedMcNemarEvidence | null\n /** Exact one-sided sign-test p-value on the small-sample bootstrap path;\n * null otherwise. */\n pValue: number | null\n /** Effective observation minimum for the target, confidence, and caller floor. */\n minimumPairs: number\n /** n >= minimumPairs. */\n sufficient: boolean\n /** The deciding interval is zero-width at numeric precision or non-finite — no evidence in either\n * direction, so it cannot clear any threshold on evidence. */\n indeterminate: boolean\n /** McNemar's exact test refuses at a non-negative threshold. */\n exactTestVetoes: boolean\n /** The deciding interval clears the threshold, ignoring the other two guards. */\n clearsThreshold: boolean\n /** `sufficient && !indeterminate && clearsThreshold && !exactTestVetoes` —\n * the whole rule. */\n promote: boolean\n /** What `delta` measures, for a reason string. */\n label: 'success-rate' | 'mean' | 'median'\n /** Why a zero-width interval is zero-width; empty when it is not. */\n indeterminateCause: string\n /** Sentence naming the test when the exact sign test decided; else empty. */\n methodDetail: string\n}\n\n/** The shape facts that pick the estimator, without computing an interval. */\nexport interface PairedDecisionShape {\n statistic: PairedDecisionStatistic\n /** Declared or inferred positive binary level; null off the binary path. */\n binaryScale: number | null\n /** Exact-tie fraction over the paired deltas; null when there are no pairs. */\n tieFraction: number | null\n}\n\n/**\n * Which estimator {@link decidePairedPromotion} would use on this data, and the\n * shape facts behind it — for callers that must report the shape on a path\n * where no interval is computed at all (an early rejection, or zero pairs).\n * Cheap: no bootstrap, no interval.\n */\nexport function pairedDecisionShape(\n before: number[],\n after: number[],\n statistic: 'mean' | 'median' = 'mean',\n declaredBinaryScale?: number,\n continuous = false,\n): PairedDecisionShape {\n if (continuous && declaredBinaryScale !== undefined) {\n throw new Error('pairedDecisionShape: continuous cannot accompany binaryScale')\n }\n if (declaredBinaryScale !== undefined) {\n if (!Number.isFinite(declaredBinaryScale) || declaredBinaryScale <= 0) {\n throw new Error('pairedDecisionShape: binaryScale must be finite and positive')\n }\n if (statistic === 'median') {\n throw new Error('pairedDecisionShape: binaryScale requires the mean statistic, not median')\n }\n for (const [name, arm] of [\n ['before', before],\n ['after', after],\n ] as const) {\n for (let i = 0; i < arm.length; i++) {\n const value = arm[i]!\n if (value !== 0 && value !== declaredBinaryScale) {\n throw new Error(\n `pairedDecisionShape: ${name}[${i}] must be 0 or binaryScale (${declaredBinaryScale}); got ${value}`,\n )\n }\n }\n }\n }\n const tieFraction = before.length === 0 ? null : pairedDeltaTieFraction(before, after)\n if (statistic === 'median') {\n return { statistic: 'median_bootstrap', binaryScale: null, tieFraction }\n }\n if (continuous) {\n return { statistic: 'mean_bootstrap', binaryScale: null, tieFraction }\n }\n const binaryScale = declaredBinaryScale ?? pairedBinaryScale(before, after)\n if (binaryScale !== null) {\n return { statistic: 'paired_risk_difference', binaryScale, tieFraction }\n }\n return { statistic: 'mean_bootstrap', binaryScale: null, tieFraction }\n}\n\n/**\n * Decide whether a paired candidate-minus-baseline delta clears a promotion\n * threshold. `before` is the baseline arm, `after` the candidate arm, paired by\n * position. Throws on unequal lengths.\n */\nexport function decidePairedPromotion(\n before: number[],\n after: number[],\n options: PairedPromotionDecisionOptions = {},\n): PairedPromotionDecision {\n if (before.length !== after.length) {\n throw new Error(\n `decidePairedPromotion: unequal sample sizes (${before.length} vs ${after.length})`,\n )\n }\n const threshold = options.threshold ?? 0\n if (!Number.isFinite(threshold)) {\n throw new Error(`decidePairedPromotion: threshold must be finite, got ${threshold}`)\n }\n const confidence = options.confidence ?? 0.95\n const exactMinimum = minimumPairsForPairedDeltaTest(confidence)\n const requestedMinimum = options.minPairs ?? exactMinimum\n if (!Number.isInteger(requestedMinimum) || requestedMinimum < 1) {\n throw new Error(\n `decidePairedPromotion: minPairs must be a positive integer, got ${requestedMinimum}`,\n )\n }\n const { binaryScale, tieFraction } = pairedDecisionShape(\n before,\n after,\n options.statistic,\n options.binaryScale,\n options.continuous ?? false,\n )\n const estimatorMinimum =\n binaryScale === null && options.statistic !== 'median' ? BOOTSTRAP_GATE_MIN_N : exactMinimum\n const minimumPairs = Math.max(requestedMinimum, exactMinimum, estimatorMinimum)\n const n = before.length\n const sufficient = n >= minimumPairs\n\n let core: {\n statistic: PairedDecisionStatistic\n method: PairedDecisionMethod\n delta: number\n low: number\n high: number\n bootstrap: PairedBootstrapResult | null\n mcnemar: PairedMcNemarEvidence | null\n pValue: number | null\n clearsThreshold: boolean\n label: PairedPromotionDecision['label']\n methodDetail: string\n }\n\n if (binaryScale !== null) {\n // Normalise the two-point encoding to {0,1} so the estimators see the\n // pass/fail structure, then rescale the answer back into the caller's\n // native units — the threshold is read in the units of the scores, so a\n // 0-100 pass/fail dimension must be gated in points, not in a rate.\n const unitControl = before.map((v) => v / binaryScale)\n const unitTreatment = after.map((v) => v / binaryScale)\n // TWO estimators with different jobs, because no single one does both.\n // `exact` is the authority on RD = 0 and supplies the veto; its\n // Clopper-Pearson interval conditions on the discordant count and is\n // therefore NOT a confidence interval at a nonzero margin. `score` is\n // Tango's, which is.\n const exact = pairedRiskDifferenceExact(unitControl, unitTreatment, confidence)\n const score = pairedRiskDifferenceScore(unitControl, unitTreatment, confidence)\n const low = score.lower * binaryScale\n core = {\n statistic: 'paired_risk_difference',\n method: 'score-interval',\n delta: score.riskDifference * binaryScale,\n low,\n high: score.upper * binaryScale,\n bootstrap: null,\n mcnemar: {\n b: exact.b,\n c: exact.c,\n nDiscordant: exact.nDiscordant,\n pValue: exact.pValue,\n },\n pValue: null,\n clearsThreshold: low > threshold,\n label: 'success-rate',\n methodDetail: '',\n }\n } else {\n const bootstrapStatistic = options.statistic === 'median' ? 'median' : 'mean'\n const test = pairedDeltaTest(before, after, {\n confidence,\n resamples: options.resamples,\n statistic: bootstrapStatistic,\n seed: options.seed,\n threshold,\n minPairs: minimumPairs,\n })\n const ci = test.bootstrap\n core = {\n statistic: bootstrapStatistic === 'mean' ? 'mean_bootstrap' : 'median_bootstrap',\n method: test.method,\n delta: bootstrapStatistic === 'mean' ? ci.mean : ci.median,\n low: ci.low,\n high: ci.high,\n bootstrap: ci,\n mcnemar: null,\n pValue: test.pValue,\n clearsThreshold: test.significant,\n label: bootstrapStatistic,\n methodDetail:\n test.method === 'exact-sign'\n ? bootstrapStatistic === 'mean'\n ? ` The mean requires ${minimumPairs} pairs for bootstrap eligibility;` +\n ` the exact sign-test p=${fmt(test.pValue ?? 1)} does not establish a mean effect.`\n : ` Below ${BOOTSTRAP_GATE_MIN_N} pairs the interval is descriptive only;` +\n ` the median decision uses the exact one-sided sign test, p=${fmt(test.pValue ?? 1)}.`\n : '',\n }\n }\n\n // Equivalent deltas can differ by rounding after subtraction and averaging.\n // Use the interval's own scale so tiny score units retain the same decision\n // as a rescaled copy of the evidence.\n const intervalScale = Math.max(Math.abs(core.low), Math.abs(core.high))\n const intervalTolerance = intervalScale * Number.EPSILON * 8\n const indeterminate =\n !Number.isFinite(core.low) ||\n !Number.isFinite(core.high) ||\n core.high - core.low <= intervalTolerance\n const indeterminateCause = !indeterminate\n ? ''\n : tieFraction === 1\n ? 'every paired delta is an exact tie'\n : core.mcnemar !== null && core.mcnemar.nDiscordant === 0\n ? 'every pair is concordant (0 discordant pairs)'\n : `the ${core.label} CI collapsed to a point at ${fmt(core.low)}`\n // Only at a non-negative threshold: a negative threshold asks a\n // noninferiority question, which McNemar's test of \"no difference\" does not\n // answer.\n const exactTestVetoes =\n core.mcnemar !== null && threshold >= 0 && !(core.mcnemar.pValue < 1 - confidence)\n\n return {\n n,\n threshold,\n confidence,\n binaryScale,\n tieFraction,\n minimumPairs,\n sufficient,\n indeterminate,\n indeterminateCause,\n exactTestVetoes,\n promote: sufficient && !indeterminate && core.clearsThreshold && !exactTestVetoes,\n ...core,\n }\n}\n\nfunction fmt(x: number): string {\n return x.toFixed(4)\n}\n"],"mappings":";;;;AAgCA,SAAgB,+BAA+B,aAAa,KAAc;CACxE,IAAI,CAAC,OAAO,SAAS,UAAU,KAAK,cAAc,KAAK,cAAc,GACnE,MAAM,IAAI,MACR,oEAAoE,YACtE;CAEF,MAAM,iBAAiB,IAAI,cAAc;CACzC,OAAO,KAAK,KAAK,KAAK,KAAK,IAAI,aAAa,CAAC;AAC/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,gBACd,QACA,OACA,UAAkC,CAAC,GACZ;CACvB,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI,CAAC,OAAO,SAAS,SAAS,GAC5B,MAAM,IAAI,MAAM,kDAAkD,WAAW;CAG/E,MAAM,eAAe,+BADF,QAAQ,cAAc,GACqB;CAC9D,MAAM,mBAAmB,QAAQ,YAAY;CAC7C,IAAI,CAAC,OAAO,UAAU,gBAAgB,KAAK,mBAAmB,GAC5D,MAAM,IAAI,MAAM,6DAA6D,kBAAkB;CAEjG,MAAM,eAAe,KAAK,IAAI,kBAAkB,YAAY;CAC5D,MAAM,YAAY,gBAAgB,QAAQ,OAAO,OAAO;CACxD,MAAM,aAAa,UAAU,KAAK;CAIlC,MAAM,gBACJ,UAAU,IAAI,MACb,CAAC,OAAO,SAAS,UAAU,GAAG,KAC7B,CAAC,OAAO,SAAS,UAAU,IAAI,KAC/B,UAAU,QAAQ,UAAU;CAEhC,IAAI,UAAU,cACZ,OAAO;EACL;EACA,QAAQ;EACR,QAAQ;EACR;EACA;EACA;EACA,aAAa,cAAc,CAAC,iBAAiB,UAAU,MAAM;CAC/D;CAGF,MAAM,cAAc,OAAO,KAAK,OAAO,UAAU,MAAM,SAAU,QAAQ,SAAS;CAClF,MAAM,QAAQ,eAAe,aAAa,SAAS;CACnD,MAAM,WAAW,QAAQ,cAAc,SAAS,UAAU,OAAO,UAAU;CAC3E,OAAO;EACL;EACA,QAAQ;EACR,QAAQ,MAAM;EACd;EACA;EACA;EACA,aACE,cACA,CAAC,iBACD,WAAW,aACX,MAAM,WAAW,IAAI,UAAU,cAAc;CACjD;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACuDA,SAAgB,oBACd,QACA,OACA,YAA+B,QAC/B,qBACA,aAAa,OACQ;CACrB,IAAI,cAAc,wBAAwB,KAAA,GACxC,MAAM,IAAI,MAAM,8DAA8D;CAEhF,IAAI,wBAAwB,KAAA,GAAW;EACrC,IAAI,CAAC,OAAO,SAAS,mBAAmB,KAAK,uBAAuB,GAClE,MAAM,IAAI,MAAM,8DAA8D;EAEhF,IAAI,cAAc,UAChB,MAAM,IAAI,MAAM,0EAA0E;EAE5F,KAAK,MAAM,CAAC,MAAM,QAAQ,CACxB,CAAC,UAAU,MAAM,GACjB,CAAC,SAAS,KAAK,CACjB,GACE,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;GACnC,MAAM,QAAQ,IAAI;GAClB,IAAI,UAAU,KAAK,UAAU,qBAC3B,MAAM,IAAI,MACR,wBAAwB,KAAK,GAAG,EAAE,8BAA8B,oBAAoB,SAAS,OAC/F;EAEJ;CAEJ;CACA,MAAM,cAAc,OAAO,WAAW,IAAI,OAAO,uBAAuB,QAAQ,KAAK;CACrF,IAAI,cAAc,UAChB,OAAO;EAAE,WAAW;EAAoB,aAAa;EAAM;CAAY;CAEzE,IAAI,YACF,OAAO;EAAE,WAAW;EAAkB,aAAa;EAAM;CAAY;CAEvE,MAAM,cAAc,uBAAuB,kBAAkB,QAAQ,KAAK;CAC1E,IAAI,gBAAgB,MAClB,OAAO;EAAE,WAAW;EAA0B;EAAa;CAAY;CAEzE,OAAO;EAAE,WAAW;EAAkB,aAAa;EAAM;CAAY;AACvE;;;;;;AAOA,SAAgB,sBACd,QACA,OACA,UAA0C,CAAC,GAClB;CACzB,IAAI,OAAO,WAAW,MAAM,QAC1B,MAAM,IAAI,MACR,gDAAgD,OAAO,OAAO,MAAM,MAAM,OAAO,EACnF;CAEF,MAAM,YAAY,QAAQ,aAAa;CACvC,IAAI,CAAC,OAAO,SAAS,SAAS,GAC5B,MAAM,IAAI,MAAM,wDAAwD,WAAW;CAErF,MAAM,aAAa,QAAQ,cAAc;CACzC,MAAM,eAAe,+BAA+B,UAAU;CAC9D,MAAM,mBAAmB,QAAQ,YAAY;CAC7C,IAAI,CAAC,OAAO,UAAU,gBAAgB,KAAK,mBAAmB,GAC5D,MAAM,IAAI,MACR,mEAAmE,kBACrE;CAEF,MAAM,EAAE,aAAa,gBAAgB,oBACnC,QACA,OACA,QAAQ,WACR,QAAQ,aACR,QAAQ,cAAc,KACxB;CACA,MAAM,mBACJ,gBAAgB,QAAQ,QAAQ,cAAc,WAAA,KAAkC;CAClF,MAAM,eAAe,KAAK,IAAI,kBAAkB,cAAc,gBAAgB;CAC9E,MAAM,IAAI,OAAO;CACjB,MAAM,aAAa,KAAK;CAExB,IAAI;CAcJ,IAAI,gBAAgB,MAAM;EAKxB,MAAM,cAAc,OAAO,KAAK,MAAM,IAAI,WAAW;EACrD,MAAM,gBAAgB,MAAM,KAAK,MAAM,IAAI,WAAW;EAMtD,MAAM,QAAQ,0BAA0B,aAAa,eAAe,UAAU;EAC9E,MAAM,QAAQ,0BAA0B,aAAa,eAAe,UAAU;EAC9E,MAAM,MAAM,MAAM,QAAQ;EAC1B,OAAO;GACL,WAAW;GACX,QAAQ;GACR,OAAO,MAAM,iBAAiB;GAC9B;GACA,MAAM,MAAM,QAAQ;GACpB,WAAW;GACX,SAAS;IACP,GAAG,MAAM;IACT,GAAG,MAAM;IACT,aAAa,MAAM;IACnB,QAAQ,MAAM;GAChB;GACA,QAAQ;GACR,iBAAiB,MAAM;GACvB,OAAO;GACP,cAAc;EAChB;CACF,OAAO;EACL,MAAM,qBAAqB,QAAQ,cAAc,WAAW,WAAW;EACvE,MAAM,OAAO,gBAAgB,QAAQ,OAAO;GAC1C;GACA,WAAW,QAAQ;GACnB,WAAW;GACX,MAAM,QAAQ;GACd;GACA,UAAU;EACZ,CAAC;EACD,MAAM,KAAK,KAAK;EAChB,OAAO;GACL,WAAW,uBAAuB,SAAS,mBAAmB;GAC9D,QAAQ,KAAK;GACb,OAAO,uBAAuB,SAAS,GAAG,OAAO,GAAG;GACpD,KAAK,GAAG;GACR,MAAM,GAAG;GACT,WAAW;GACX,SAAS;GACT,QAAQ,KAAK;GACb,iBAAiB,KAAK;GACtB,OAAO;GACP,cACE,KAAK,WAAW,eACZ,uBAAuB,SACrB,sBAAsB,aAAa,0DACT,IAAI,KAAK,UAAU,CAAC,EAAE,sCAChD,+GAC8D,IAAI,KAAK,UAAU,CAAC,EAAE,KACtF;EACR;CACF;CAMA,MAAM,oBADgB,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,GAAG,KAAK,IAAI,KAAK,IAAI,CAC/B,IAAI,OAAO,UAAU;CAC3D,MAAM,gBACJ,CAAC,OAAO,SAAS,KAAK,GAAG,KACzB,CAAC,OAAO,SAAS,KAAK,IAAI,KAC1B,KAAK,OAAO,KAAK,OAAO;CAC1B,MAAM,qBAAqB,CAAC,gBACxB,KACA,gBAAgB,IACd,uCACA,KAAK,YAAY,QAAQ,KAAK,QAAQ,gBAAgB,IACpD,kDACA,OAAO,KAAK,MAAM,8BAA8B,IAAI,KAAK,GAAG;CAIpE,MAAM,kBACJ,KAAK,YAAY,QAAQ,aAAa,KAAK,EAAE,KAAK,QAAQ,SAAS,IAAI;CAEzE,OAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,SAAS,cAAc,CAAC,iBAAiB,KAAK,mBAAmB,CAAC;EAClE,GAAG;CACL;AACF;AAEA,SAAS,IAAI,GAAmB;CAC9B,OAAO,EAAE,QAAQ,CAAC;AACpB"}
1
+ {"version":3,"file":"paired-promotion-decision-BN6IQTiu.js","names":[],"sources":["../src/paired-delta-test.ts","../src/paired-promotion-decision.ts"],"sourcesContent":["import {\n type PairedBootstrapOptions,\n type PairedBootstrapResult,\n pairedBootstrap,\n pairedSignTest,\n} from './statistics'\n\nexport interface PairedDeltaTestOptions extends PairedBootstrapOptions {\n /** Smallest candidate-minus-baseline delta that counts as improvement. Default 0. */\n threshold?: number\n /** Caller-required paired observations. The exact test may impose a higher minimum. */\n minPairs?: number\n}\n\nexport interface PairedDeltaTestResult {\n bootstrap: PairedBootstrapResult\n method: 'bootstrap-ci' | 'exact-sign'\n /** Exact one-sided p-value below the bootstrap minimum; otherwise null. */\n pValue: number | null\n /** Effective observation minimum after accounting for confidence. */\n minimumPairs: number\n sufficient: boolean\n /**\n * The bootstrap interval has zero width (or is non-finite), so it carries no\n * information about how far the estimate could be wrong and cannot support a\n * decision in either direction. See {@link pairedDeltaTest}.\n */\n indeterminate: boolean\n significant: boolean\n}\n\n/** Smallest all-positive sample that can clear a one-sided exact sign test. */\nexport function minimumPairsForPairedDeltaTest(confidence = 0.95): number {\n if (!Number.isFinite(confidence) || confidence <= 0 || confidence >= 1) {\n throw new Error(\n `minimumPairsForPairedDeltaTest: confidence must be in (0,1), got ${confidence}`,\n )\n }\n const oneSidedAlpha = (1 - confidence) / 2\n return Math.ceil(Math.log2(1 / oneSidedAlpha))\n}\n\n/**\n * Tests whether a paired candidate-minus-baseline delta clears a threshold.\n *\n * At 20 or more pairs, the percentile bootstrap lower bound carries the\n * decision. Below that point the interval is descriptive only, so the function\n * switches to a pre-registered one-sided exact sign test. The exact path is\n * deliberately conservative: it requires both a point estimate above the\n * threshold and enough consistently positive paired differences.\n *\n * ## A zero-width interval is never significant\n *\n * When every paired delta is identical the resample distribution is a point\n * mass and the interval collapses: `[0, 0]` when all pairs tie, `[g, g]` on n\n * identical deltas of g. Neither says the effect is certain — both say the\n * sample carries no information about how far the estimate could be wrong, and\n * `low > threshold` then answers on the point estimate alone. It fails in both\n * directions: `[0, 0]` clears every NEGATIVE threshold, which is how a\n * tie-dominated pass/fail comparison laundered a regression into a\n * noninferiority pass, and `[g, g]` clears every threshold below g with no\n * spread behind it. Under a bounded asymmetric null whose true mean paired\n * delta is exactly 0 — 2 % of pairs dropping by 1.0, the rest gaining 0.0204 —\n * every sample that misses the drop is exactly that shape, and deciding on\n * `low > 0` promoted 65.65 % of samples at n = 20 against a nominal 5 %.\n *\n * So `indeterminate` is reported and `significant` is false whenever the\n * interval has zero width, on BOTH paths: at small n the exact sign test is a\n * test of the MEDIAN and a zero-spread sample is precisely where it stops\n * saying anything about the mean the caller is thresholding.\n *\n * `threshold` may be negative — that is a noninferiority margin, and it is the\n * regime the zero-width hole is worst in. For a two-point (pass/fail) outcome\n * the percentile bootstrap is not a valid interval at a nonzero margin at all;\n * use {@link decidePairedPromotion}, which routes those to Tango's score\n * interval, rather than thresholding this function's bootstrap directly.\n */\nexport function pairedDeltaTest(\n before: number[],\n after: number[],\n options: PairedDeltaTestOptions = {},\n): PairedDeltaTestResult {\n const threshold = options.threshold ?? 0\n if (!Number.isFinite(threshold)) {\n throw new Error(`pairedDeltaTest: threshold must be finite, got ${threshold}`)\n }\n const confidence = options.confidence ?? 0.95\n const exactMinimum = minimumPairsForPairedDeltaTest(confidence)\n const requestedMinimum = options.minPairs ?? exactMinimum\n if (!Number.isInteger(requestedMinimum) || requestedMinimum < 1) {\n throw new Error(`pairedDeltaTest: minPairs must be a positive integer, got ${requestedMinimum}`)\n }\n const minimumPairs = Math.max(requestedMinimum, exactMinimum)\n const bootstrap = pairedBootstrap(before, after, options)\n const sufficient = bootstrap.n >= minimumPairs\n // A point-mass resample distribution is an absence of evidence, not a\n // certainty, and it is the shape that clears every negative threshold. Both\n // paths refuse it: see the \"zero-width\" section of this function's docs.\n const indeterminate =\n bootstrap.n > 0 &&\n (!Number.isFinite(bootstrap.low) ||\n !Number.isFinite(bootstrap.high) ||\n bootstrap.low === bootstrap.high)\n\n if (bootstrap.gateEligible) {\n return {\n bootstrap,\n method: 'bootstrap-ci',\n pValue: null,\n minimumPairs,\n sufficient,\n indeterminate,\n significant: sufficient && !indeterminate && bootstrap.low > threshold,\n }\n }\n\n const differences = before.map((value, index) => after[index]! - value - threshold)\n const exact = pairedSignTest(differences, 'greater')\n const estimate = options.statistic === 'mean' ? bootstrap.mean : bootstrap.median\n return {\n bootstrap,\n method: 'exact-sign',\n pValue: exact.pValue,\n minimumPairs,\n sufficient,\n indeterminate,\n significant:\n sufficient &&\n !indeterminate &&\n estimate > threshold &&\n exact.pValue <= (1 - bootstrap.confidence) / 2,\n }\n}\n","/**\n * @module\n * ONE rule for \"does this paired interval clear a promotion threshold\".\n *\n * The rule below was derived on `HeldOutGate` (#479) after the same estimator\n * bug shipped twice. It then turned out that a SECOND gate — the composable\n * `heldOutGate`, plus everything else routed through `heldoutSignificance` —\n * still carried the original defect, because the rule had been written into one\n * gate's method body rather than into a shared function. Two copies of a\n * statistical rule is how a defect survives in one of them, so there is now\n * exactly one copy and both gates call it.\n *\n * Three things the rule does that a bare `pairedBootstrap(...).low > threshold`\n * does not:\n *\n * 1. **Two-point (pass/fail) outcomes decide on Tango's SCORE interval.** On a\n * pass/fail eval the paired delta vector is dominated by ties, so the\n * bootstrap of the mean is a resample of a lattice with three atoms and its\n * percentile interval is not valid at a nonzero margin. The score interval\n * (`pairedRiskDifferenceScore`) re-estimates the nuisance loss rate under\n * each hypothesised margin instead of fixing it at the observed value, which\n * is the only construction that stays a confidence interval as the margin\n * moves off zero — the regime every noninferiority threshold lives in.\n * Measured on the composable gate before this change, at a true risk\n * difference sitting exactly on the production caller's -0.05 margin and a\n * nominal 5 %: 14.60 % false promotion at n = 40 and 10.10 % at n = 76.\n * 2. **McNemar's exact test holds a VETO at every non-negative threshold.**\n * Redundant with the interval by construction and kept anyway, so that\n * swapping the estimator for one without that duality cannot silently\n * reintroduce \"promotes what the exact test refuses\". Witness: n = 6, b = 5,\n * c = 0 — no exact argument reaches alpha = 0.05 with 5 discordant pairs\n * (two-sided floor 2/2^5 = 0.0625), whatever an interval says. A NEGATIVE\n * threshold is a noninferiority question, which McNemar's test of \"no\n * difference\" is not the right test for, so the veto does not apply there.\n * 3. **A ZERO-WIDTH interval is refused, wherever it sits.** At [0, 0] it\n * cannot tell a gain from a regression and clears every negative threshold.\n * Away from zero it fails the opposite way: n identical positive deltas give\n * [g, g], which clears threshold 0 on no spread at all. Both are an absence\n * of evidence. Measured on the composable gate before this change, under a\n * bounded asymmetric null whose true mean paired delta is exactly 0: 88.50 %\n * false promotion at n = 6 and 65.65 % at n = 20 against a nominal 5 %.\n *\n * Eligibility follows the requested target. A continuous mean needs the\n * bootstrap minimum; binary outcomes and explicit median targets use the\n * exact-test minimum. A small-sample sign diagnostic cannot certify a mean\n * effect. These floors establish estimator eligibility, not adequate power.\n */\n\nimport { minimumPairsForPairedDeltaTest, pairedDeltaTest } from './paired-delta-test'\nimport {\n BOOTSTRAP_GATE_MIN_N,\n type PairedBootstrapResult,\n pairedBinaryScale,\n pairedDeltaTieFraction,\n pairedRiskDifferenceExact,\n pairedRiskDifferenceScore,\n} from './statistics'\n\n/** Which paired estimator produced the deciding interval. */\nexport type PairedDecisionStatistic =\n | 'paired_risk_difference'\n | 'mean_bootstrap'\n | 'median_bootstrap'\n\n/** Which test carried the decision, given the estimator and the sample size. */\nexport type PairedDecisionMethod = 'score-interval' | 'bootstrap-ci' | 'exact-sign'\n\n/** McNemar's exact paired-binary evidence, on the two-point path only. */\nexport interface PairedMcNemarEvidence {\n /** Discordant pairs the treatment won. */\n b: number\n /** Discordant pairs the control won. */\n c: number\n /** b + c — the only pairs carrying information. */\n nDiscordant: number\n /** Two-sided exact p-value. */\n pValue: number\n}\n\nexport interface PairedPromotionDecisionOptions {\n /** Smallest candidate-minus-baseline delta that counts as improvement, in the\n * caller's native units. May be negative (a noninferiority margin). Default 0. */\n threshold?: number\n /** Confidence level. Default 0.95. */\n confidence?: number\n /** Bootstrap resamples, on the paths where a bootstrap decides. Default 2000. */\n resamples?: number\n /** Deterministic bootstrap seed. Omitted ⇒ derived from the deltas. */\n seed?: number\n /** Caller-required paired observations. The test also imposes its own\n * minimum: bootstrap eligibility for a continuous mean, or the exact-test\n * minimum for binary and explicit median targets. This is not a power guarantee. */\n minPairs?: number\n /**\n * `'mean'` (default) routes by SHAPE: a two-point (pass/fail) outcome on any\n * encoding decides on the score interval, everything else on the mean\n * bootstrap. `'median'` forces the median bootstrap on every input, including\n * shapes where it is structurally blind — kept for callers who want outlier\n * robustness on genuinely continuous outcomes and accept that cost.\n */\n statistic?: 'mean' | 'median'\n /** Declared binary support {0, binaryScale}, including when all observations\n * are zero. Must be finite and positive; every observation must use that\n * support. Uses the risk-difference mean, so cannot accompany 'median'.\n * Omitted: infer binary support from observed positive values. */\n binaryScale?: number\n /**\n * Declare the outcome continuous: never infer a two-point support from the\n * observed values, so the estimator is fixed before the data is seen. A\n * sealed call on a continuous measure must route to the bootstrap whatever\n * the sample happens to look like; with inference on, a sample whose values\n * all land on {0, s} is silently decided on the score interval instead.\n * Measured under discovery's E1 alternative (deltas in {+1, -0.5, 0} on a\n * zero baseline) at 20 pairs: 5.4 % of 2,000 simulated samples (seed 11)\n * re-route without this flag, 0 with it; the exact rate is 5.54 %\n * (`paired-promotion-power.test.ts`). Cannot accompany `binaryScale`.\n */\n continuous?: boolean\n}\n\nexport interface PairedPromotionDecision {\n /** Paired observations supplied. */\n n: number\n /** Threshold the interval was judged against, native units. */\n threshold: number\n confidence: number\n statistic: PairedDecisionStatistic\n method: PairedDecisionMethod\n /** Declared or inferred positive level ({0,1} ⇒ 1, {0,100} ⇒ 100),\n * or null when the outcome is not two-point. Non-null is exactly the\n * condition for the `paired_risk_difference` path, and it is the factor\n * `delta` / `low` / `high` were rescaled by. */\n binaryScale: number | null\n /** Exact-tie fraction over the paired deltas; null when there are no pairs. */\n tieFraction: number | null\n /** Point estimate of the DECIDING statistic, in the caller's native units. */\n delta: number\n /** Lower bound of the DECIDING interval, native units. */\n low: number\n /** Upper bound of the DECIDING interval, native units. */\n high: number\n /** The bootstrap that decided, or null when the score interval did. Callers\n * that need a bootstrap as a diagnostic on the two-point path compute their\n * own — it is not computed here, so the binary path costs no resamples. */\n bootstrap: PairedBootstrapResult | null\n /** McNemar's exact evidence, or null off the two-point path. */\n mcnemar: PairedMcNemarEvidence | null\n /** Exact one-sided sign-test p-value on the small-sample bootstrap path;\n * null otherwise. */\n pValue: number | null\n /** Effective observation minimum for the target, confidence, and caller floor. */\n minimumPairs: number\n /** n >= minimumPairs. */\n sufficient: boolean\n /** The deciding interval is zero-width at numeric precision or non-finite — no evidence in either\n * direction, so it cannot clear any threshold on evidence. */\n indeterminate: boolean\n /** McNemar's exact test refuses at a non-negative threshold. */\n exactTestVetoes: boolean\n /** The deciding interval clears the threshold, ignoring the other two guards. */\n clearsThreshold: boolean\n /** `sufficient && !indeterminate && clearsThreshold && !exactTestVetoes` —\n * the whole rule. */\n promote: boolean\n /** What `delta` measures, for a reason string. */\n label: 'success-rate' | 'mean' | 'median'\n /** Why a zero-width interval is zero-width; empty when it is not. */\n indeterminateCause: string\n /** Sentence naming the test when the exact sign test decided; else empty. */\n methodDetail: string\n}\n\n/** The shape facts that pick the estimator, without computing an interval. */\nexport interface PairedDecisionShape {\n statistic: PairedDecisionStatistic\n /** Declared or inferred positive binary level; null off the binary path. */\n binaryScale: number | null\n /** Exact-tie fraction over the paired deltas; null when there are no pairs. */\n tieFraction: number | null\n}\n\n/**\n * Which estimator {@link decidePairedPromotion} would use on this data, and the\n * shape facts behind it — for callers that must report the shape on a path\n * where no interval is computed at all (an early rejection, or zero pairs).\n * Cheap: no bootstrap, no interval.\n */\nexport function pairedDecisionShape(\n before: number[],\n after: number[],\n statistic: 'mean' | 'median' = 'mean',\n declaredBinaryScale?: number,\n continuous = false,\n): PairedDecisionShape {\n if (continuous && declaredBinaryScale !== undefined) {\n throw new Error('pairedDecisionShape: continuous cannot accompany binaryScale')\n }\n if (declaredBinaryScale !== undefined) {\n if (!Number.isFinite(declaredBinaryScale) || declaredBinaryScale <= 0) {\n throw new Error('pairedDecisionShape: binaryScale must be finite and positive')\n }\n if (statistic === 'median') {\n throw new Error('pairedDecisionShape: binaryScale requires the mean statistic, not median')\n }\n for (const [name, arm] of [\n ['before', before],\n ['after', after],\n ] as const) {\n for (let i = 0; i < arm.length; i++) {\n const value = arm[i]!\n if (value !== 0 && value !== declaredBinaryScale) {\n throw new Error(\n `pairedDecisionShape: ${name}[${i}] must be 0 or binaryScale (${declaredBinaryScale}); got ${value}`,\n )\n }\n }\n }\n }\n const tieFraction = before.length === 0 ? null : pairedDeltaTieFraction(before, after)\n if (statistic === 'median') {\n return { statistic: 'median_bootstrap', binaryScale: null, tieFraction }\n }\n if (continuous) {\n return { statistic: 'mean_bootstrap', binaryScale: null, tieFraction }\n }\n const binaryScale = declaredBinaryScale ?? pairedBinaryScale(before, after)\n if (binaryScale !== null) {\n return { statistic: 'paired_risk_difference', binaryScale, tieFraction }\n }\n return { statistic: 'mean_bootstrap', binaryScale: null, tieFraction }\n}\n\n/**\n * Decide whether a paired candidate-minus-baseline delta clears a promotion\n * threshold. `before` is the baseline arm, `after` the candidate arm, paired by\n * position. Throws on unequal lengths.\n */\nexport function decidePairedPromotion(\n before: number[],\n after: number[],\n options: PairedPromotionDecisionOptions = {},\n): PairedPromotionDecision {\n if (before.length !== after.length) {\n throw new Error(\n `decidePairedPromotion: unequal sample sizes (${before.length} vs ${after.length})`,\n )\n }\n const threshold = options.threshold ?? 0\n if (!Number.isFinite(threshold)) {\n throw new Error(`decidePairedPromotion: threshold must be finite, got ${threshold}`)\n }\n const confidence = options.confidence ?? 0.95\n const exactMinimum = minimumPairsForPairedDeltaTest(confidence)\n const requestedMinimum = options.minPairs ?? exactMinimum\n if (!Number.isInteger(requestedMinimum) || requestedMinimum < 1) {\n throw new Error(\n `decidePairedPromotion: minPairs must be a positive integer, got ${requestedMinimum}`,\n )\n }\n const { binaryScale, tieFraction } = pairedDecisionShape(\n before,\n after,\n options.statistic,\n options.binaryScale,\n options.continuous ?? false,\n )\n const estimatorMinimum =\n binaryScale === null && options.statistic !== 'median' ? BOOTSTRAP_GATE_MIN_N : exactMinimum\n const minimumPairs = Math.max(requestedMinimum, exactMinimum, estimatorMinimum)\n const n = before.length\n const sufficient = n >= minimumPairs\n\n let core: {\n statistic: PairedDecisionStatistic\n method: PairedDecisionMethod\n delta: number\n low: number\n high: number\n bootstrap: PairedBootstrapResult | null\n mcnemar: PairedMcNemarEvidence | null\n pValue: number | null\n clearsThreshold: boolean\n label: PairedPromotionDecision['label']\n methodDetail: string\n }\n\n if (binaryScale !== null) {\n // Normalise the two-point encoding to {0,1} so the estimators see the\n // pass/fail structure, then rescale the answer back into the caller's\n // native units — the threshold is read in the units of the scores, so a\n // 0-100 pass/fail dimension must be gated in points, not in a rate.\n const unitControl = before.map((v) => v / binaryScale)\n const unitTreatment = after.map((v) => v / binaryScale)\n // TWO estimators with different jobs, because no single one does both.\n // `exact` is the authority on RD = 0 and supplies the veto; its\n // Clopper-Pearson interval conditions on the discordant count and is\n // therefore NOT a confidence interval at a nonzero margin. `score` is\n // Tango's, which is.\n const exact = pairedRiskDifferenceExact(unitControl, unitTreatment, confidence)\n const score = pairedRiskDifferenceScore(unitControl, unitTreatment, confidence)\n const low = score.lower * binaryScale\n core = {\n statistic: 'paired_risk_difference',\n method: 'score-interval',\n delta: score.riskDifference * binaryScale,\n low,\n high: score.upper * binaryScale,\n bootstrap: null,\n mcnemar: {\n b: exact.b,\n c: exact.c,\n nDiscordant: exact.nDiscordant,\n pValue: exact.pValue,\n },\n pValue: null,\n clearsThreshold: low > threshold,\n label: 'success-rate',\n methodDetail: '',\n }\n } else {\n const bootstrapStatistic = options.statistic === 'median' ? 'median' : 'mean'\n const test = pairedDeltaTest(before, after, {\n confidence,\n resamples: options.resamples,\n statistic: bootstrapStatistic,\n seed: options.seed,\n threshold,\n minPairs: minimumPairs,\n })\n const ci = test.bootstrap\n core = {\n statistic: bootstrapStatistic === 'mean' ? 'mean_bootstrap' : 'median_bootstrap',\n method: test.method,\n delta: bootstrapStatistic === 'mean' ? ci.mean : ci.median,\n low: ci.low,\n high: ci.high,\n bootstrap: ci,\n mcnemar: null,\n pValue: test.pValue,\n clearsThreshold: test.significant,\n label: bootstrapStatistic,\n methodDetail:\n test.method === 'exact-sign'\n ? bootstrapStatistic === 'mean'\n ? ` The mean requires ${minimumPairs} pairs for bootstrap eligibility;` +\n ` the exact sign-test p=${fmt(test.pValue ?? 1)} does not establish a mean effect.`\n : ` Below ${BOOTSTRAP_GATE_MIN_N} pairs the interval is descriptive only;` +\n ` the median decision uses the exact one-sided sign test, p=${fmt(test.pValue ?? 1)}.`\n : '',\n }\n }\n\n // Equivalent deltas can differ by rounding after subtraction and averaging.\n // Use the interval's own scale so tiny score units retain the same decision\n // as a rescaled copy of the evidence.\n const intervalScale = Math.max(Math.abs(core.low), Math.abs(core.high))\n const intervalTolerance = intervalScale * Number.EPSILON * 8\n const indeterminate =\n !Number.isFinite(core.low) ||\n !Number.isFinite(core.high) ||\n core.high - core.low <= intervalTolerance\n const indeterminateCause = !indeterminate\n ? ''\n : tieFraction === 1\n ? 'every paired delta is an exact tie'\n : core.mcnemar !== null && core.mcnemar.nDiscordant === 0\n ? 'every pair is concordant (0 discordant pairs)'\n : `the ${core.label} CI collapsed to a point at ${fmt(core.low)}`\n // Only at a non-negative threshold: a negative threshold asks a\n // noninferiority question, which McNemar's test of \"no difference\" does not\n // answer.\n const exactTestVetoes =\n core.mcnemar !== null && threshold >= 0 && !(core.mcnemar.pValue < 1 - confidence)\n\n return {\n n,\n threshold,\n confidence,\n binaryScale,\n tieFraction,\n minimumPairs,\n sufficient,\n indeterminate,\n indeterminateCause,\n exactTestVetoes,\n promote: sufficient && !indeterminate && core.clearsThreshold && !exactTestVetoes,\n ...core,\n }\n}\n\nfunction fmt(x: number): string {\n return x.toFixed(4)\n}\n"],"mappings":"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"}
@@ -3,7 +3,7 @@ import { a as RunFilter, s as TraceStore } from "../store-C4avFjIi.js";
3
3
  import { n as FailureClusterReport, r as failureClusterView, t as FailureCluster } from "../failure-cluster-CbgEDcFJ.js";
4
4
  import { t as DiagnosisSpan } from "../spans-D9_ehFuk.js";
5
5
  import { n as TrajectoryStep } from "../trajectory-Y1fmdlw7.js";
6
- import { c as computeToolUseMetrics, d as judgeAgreementView, f as BudgetBreachFinding, g as BaselineReport, h as BaselineOptions, i as toolWasteView, l as JudgeAgreementReport, m as budgetBreachView, n as ToolWasteOptions, p as BudgetBreachReport, r as ToolWasteReport, t as ToolWasteFinding, u as JudgePair } from "../tool-waste-DxlebQkx.js";
6
+ import { c as computeToolUseMetrics, d as judgeAgreementView, f as BudgetBreachFinding, g as BaselineReport, h as BaselineOptions, i as toolWasteView, l as JudgeAgreementReport, m as budgetBreachView, n as ToolWasteOptions, p as BudgetBreachReport, r as ToolWasteReport, t as ToolWasteFinding, u as JudgePair } from "../tool-waste-Wi4g6Ekb.js";
7
7
  //#region src/pipelines/first-divergence.d.ts
8
8
  /** One step of a run, as the diff reads it. */
9
9
  export interface DiffStep {
@@ -1,7 +1,7 @@
1
1
  import { t as compareToBaseline } from "../baseline-Dm8966du.js";
2
- import { a as budgetBreachView, i as failureClusterView, n as computeToolUseMetrics, r as judgeAgreementView, t as toolWasteView } from "../tool-waste-x_E8h9Ih.js";
2
+ import { a as budgetBreachView, i as failureClusterView, n as computeToolUseMetrics, r as judgeAgreementView, t as toolWasteView } from "../tool-waste-BYt5rdt8.js";
3
3
  import { a as isToolSpan } from "../schema-CSf6qWgZ.js";
4
- import { a as hasCapturedToolArgs, r as argHash, u as runMetricExtractor } from "../query-DlV0pdx4.js";
4
+ import { a as hasCapturedToolArgs, l as runMetricExtractor, r as argHash } from "../query-B1D1qp2Z.js";
5
5
  import { t as buildTrajectory } from "../trajectory-D_7rLrvE.js";
6
6
  import { t as executionTrackByLane } from "../execution-tracks-CpgFPpS5.js";
7
7
  //#region src/pipelines/first-divergence.ts
@@ -1,5 +1,5 @@
1
1
  import { b as CustomTokenPricing } from "./cost-ledger-DbQdN3nO.js";
2
- import { f as AnalystUsageReceipt } from "./types-DAPpAkd4.js";
2
+ import { f as AnalystUsageReceipt } from "./types-KEkKkETv.js";
3
3
  import { t as PrimeBridgeTransport } from "./prime-bridge-transport-6feEglLf.js";
4
4
  //#region src/analyst/reply-contract.d.ts
5
5
  /**
@@ -328,4 +328,4 @@ interface PrimeProtocolIdentity {
328
328
  declare function primeProtocolSha256(identity: PrimeProtocolIdentity): string;
329
329
  //#endregion
330
330
  export { ReplyRowDecoded as A, primeProtocolSha256 as C, DecodedReply as D, runPrimeExchange as E, decodeReplyRows as M, ReplyContract as O, normalizePrimeUsage as S, projectPrimeTrajectory as T, buildPrimePrompt as _, PrimeProjectionOutcome as a, extractPrimeJsonObject as b, PrimeProtocolIdentity as c, PrimeRepairPromptSpec as d, PrimeRepairState as f, analystUsageReceiptFromPrimeUsage as g, PrimeTurnRecord as h, PrimeProjectionDelivery as i, ReplyRowRejection as j, ReplyEnvelope as k, PrimeRawUsage as l, PrimeRowDecoded as m, PrimeExchangeOutcome as n, PrimeProjectionSource as o, PrimeReplyContract as p, PrimeFailure as r, PrimePromptSpec as s, PrimeExchangeOptions as t, PrimeRejectedRow as u, buildPrimeRepairPrompt as v, primeReplyDefect as w, mergePrimeRawUsage as x, emptyPrimeRawUsage as y };
331
- //# sourceMappingURL=prime-protocol-Csr9kcOM.d.ts.map
331
+ //# sourceMappingURL=prime-protocol-B7et3tI7.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"prime-protocol-Csr9kcOM.d.ts","names":[],"sources":["../src/analyst/reply-contract.ts","../src/analyst/prime-protocol.ts"],"mappings":";;;;;;;;;;;;;;KAaY,gBAAgB;EAAU;EAAU,KAAK;;EAAW;EAAW;;;UAG1D;EACf;;EAEA,QAAQ;;UAGO,cAAc;;EAE7B;;EAEA;;;;;EAKA;;;;;;EAMA,UAAU,cAAc,gBAAgB,gBAAgB;;;;;;EAMxD;;;;;;EAMA,eAAe,iBAAiB;;;;;;EAMhC;;;;;EAKA;;;UAIe;EACf;EACA;;UAGe,aAAa;EAC5B,MAAM;;EAEN,QAAQ;EACR,UAAU;;EAEV;;EAEA;;;;;;iBAmCc,gBAAgB,MAC9B,UAAU,cAAc,OACxB,iBACC,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;KCjFJ,gBAAgB,QAAQ,gBAAgB;KAExC,mBAAmB,QAAQ,cAAc;UAIpC;EACf;;EAEA;EACA;;EAEA;EACA;;EAEA;;iBAGc,iBAAiB,MAAM;UAatB;EACf;EACA;EACA;;;iBAIc,uBAAuB,MAAM;;;;;;;;;;iBAuB7B,uBAAuB,eAAe;;iBAkBtC,iBACd,QAAQ,gCACR;;;;;;UAyBe;;;;;;EAMf;;EAEA;;EAEA;;;;;;;EAOA;;iBAGc,sBAAsB;;iBAKtB,oBAAoB,eAAe;;;;;;iBAgBnC,mBAAmB,GAAG,eAAe,GAAG,gBAAgB;;;;;;;;;;;;;iBA6BxD,kCACd,OAAO,eACP,SAAS,qBACR;;;;;;;;KAqCS;EAEN;EACA;;EAEA;EAAqB;EAAiB;EAAgB;;EACtD;EAAiB;EAAiB;;UAEvB;EACf;;EAEA;;;UAIe;EACf;EACA,OAAO;;EAEP;;UAGe;EACf;EACA;;KAGU,qBAAqB;EAE3B;;EAEA;EACA,MAAM;EACN,UAAU;;EAEV;;EAEA;EACA,OAAO;EACP,OAAO;EACP,QAAQ;EACR;;EAGA;EACA,SAAS;EACT,OAAO;EACP,OAAO;EACP,QAAQ;;EAER;;UAGW,qBAAqB;EACpC,UAAU,mBAAmB;EAC7B;EACA,WAAW;;EAEX;EACA;;EAEA;;EAEA;EACA,SAAS;;;;;;;iBAQW,iBAAiB,MACrC,SAAS,qBAAqB,QAC7B,QAAQ,qBAAqB;;;;;;UAyLf,sBAAsB;;EAErC,QAAQ,iBAAiB;;EAEzB,UAAU,iBAAiB;;EAE3B;;UAGe;EACf;EACA;EACA;;KAGU,uBAAuB;EAE7B;EACA,gBAAgB;EAChB;EACA,UAAU;;EAEV;EAAW;EAAgB;;;;;;;;;iBASX,uBAAuB,OAC3C,QAAQ,sBAAsB,QAC9B;EAAU;IACT,QAAQ,uBAAuB;UA8BjB;EACf;EACA;EACA;EACA;;EAEA,QAAQ,SAAS;;;;;;;;;;;;iBAaH,oBAAoB,UAAU"}
1
+ {"version":3,"file":"prime-protocol-B7et3tI7.d.ts","names":[],"sources":["../src/analyst/reply-contract.ts","../src/analyst/prime-protocol.ts"],"mappings":";;;;;;;;;;;;;;KAaY,gBAAgB;EAAU;EAAU,KAAK;;EAAW;EAAW;;;UAG1D;EACf;;EAEA,QAAQ;;UAGO,cAAc;;EAE7B;;EAEA;;;;;EAKA;;;;;;EAMA,UAAU,cAAc,gBAAgB,gBAAgB;;;;;;EAMxD;;;;;;EAMA,eAAe,iBAAiB;;;;;;EAMhC;;;;;EAKA;;;UAIe;EACf;EACA;;UAGe,aAAa;EAC5B,MAAM;;EAEN,QAAQ;EACR,UAAU;;EAEV;;EAEA;;;;;;iBAmCc,gBAAgB,MAC9B,UAAU,cAAc,OACxB,iBACC,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;KCjFJ,gBAAgB,QAAQ,gBAAgB;KAExC,mBAAmB,QAAQ,cAAc;UAIpC;EACf;;EAEA;EACA;;EAEA;EACA;;EAEA;;iBAGc,iBAAiB,MAAM;UAatB;EACf;EACA;EACA;;;iBAIc,uBAAuB,MAAM;;;;;;;;;;iBAuB7B,uBAAuB,eAAe;;iBAkBtC,iBACd,QAAQ,gCACR;;;;;;UAyBe;;;;;;EAMf;;EAEA;;EAEA;;;;;;;EAOA;;iBAGc,sBAAsB;;iBAKtB,oBAAoB,eAAe;;;;;;iBAgBnC,mBAAmB,GAAG,eAAe,GAAG,gBAAgB;;;;;;;;;;;;;iBA6BxD,kCACd,OAAO,eACP,SAAS,qBACR;;;;;;;;KAqCS;EAEN;EACA;;EAEA;EAAqB;EAAiB;EAAgB;;EACtD;EAAiB;EAAiB;;UAEvB;EACf;;EAEA;;;UAIe;EACf;EACA,OAAO;;EAEP;;UAGe;EACf;EACA;;KAGU,qBAAqB;EAE3B;;EAEA;EACA,MAAM;EACN,UAAU;;EAEV;;EAEA;EACA,OAAO;EACP,OAAO;EACP,QAAQ;EACR;;EAGA;EACA,SAAS;EACT,OAAO;EACP,OAAO;EACP,QAAQ;;EAER;;UAGW,qBAAqB;EACpC,UAAU,mBAAmB;EAC7B;EACA,WAAW;;EAEX;EACA;;EAEA;;EAEA;EACA,SAAS;;;;;;;iBAQW,iBAAiB,MACrC,SAAS,qBAAqB,QAC7B,QAAQ,qBAAqB;;;;;;UAyLf,sBAAsB;;EAErC,QAAQ,iBAAiB;;EAEzB,UAAU,iBAAiB;;EAE3B;;UAGe;EACf;EACA;EACA;;KAGU,uBAAuB;EAE7B;EACA,gBAAgB;EAChB;EACA,UAAU;;EAEV;EAAW;EAAgB;;;;;;;;;iBASX,uBAAuB,OAC3C,QAAQ,sBAAsB,QAC9B;EAAU;IACT,QAAQ,uBAAuB;UA8BjB;EACf;EACA;EACA;EACA;;EAEA,QAAQ,SAAS;;;;;;;;;;;;iBAaH,oBAAoB,UAAU"}
@@ -1,8 +1,8 @@
1
1
  import { s as ValidationError } from "./errors-Dngq5h35.js";
2
2
  import { r as canonicalString } from "./canonical-BsZVgBhW.js";
3
- import { n as JudgeParseError } from "./llm-judge-HLWASwUJ.js";
3
+ import { n as JudgeParseError } from "./llm-judge-Tr7MBUnf.js";
4
4
  import { i as CostLedger } from "./cost-ledger-_IbkejCF.js";
5
- import { E as recoverTruncatedJson } from "./campaign-evidence-DW3Krxov.js";
5
+ import { w as recoverTruncatedJson } from "./campaign-evidence-OcUciD-N.js";
6
6
  import { f as ModelSubstitutionError, h as assertServedModel, o as costReceiptFromLlm, s as costReceiptFromLlmError, u as maximumChargeForLlmRequest } from "./llm-client-v968tWw8.js";
7
7
  import { t as certificationEvidenceDigest } from "./verdict-DhPoXApk.js";
8
8
  import { createHash, randomUUID } from "node:crypto";
@@ -591,4 +591,4 @@ function extractProducedState(events) {
591
591
  //#endregion
592
592
  export { CODING_HARNESSES as a, agentProfileId as c, harnessAxisOf as d, verifyCompletion as i, agentProfileModelId as l, completionVerdict as n, HARNESS_NATIVE_MODEL as o, createLlmCorrectnessChecker as r, agentProfileHash as s, extractProducedState as t, expandProfileAxes as u };
593
593
 
594
- //# sourceMappingURL=produced-state-Bzlh09oj.js.map
594
+ //# sourceMappingURL=produced-state-CKybuR9F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"produced-state-CKybuR9F.js","names":[],"sources":["../src/agent-profile.ts","../src/completion-verifier.ts","../src/produced-state.ts"],"sourcesContent":["import { createHash } from 'node:crypto'\nimport type { AgentProfile } from '@tangle-network/agent-interface'\nimport { type HarnessType, harnessSupportsModel } from '@tangle-network/agent-interface'\nimport { ValidationError } from './errors'\nimport { canonicalString } from './ledger-core/canonical'\n\nexport type { AgentProfile, HarnessType } from '@tangle-network/agent-interface'\n\n/**\n * The agentic coding harnesses an eval sweeps by default — the ones we care about\n * ranking. This is the SINGLE source of that list; consumers import it instead of\n * re-declaring their own (a re-declared list is how the fleet drifts). Pass an\n * explicit `harnesses` (e.g. `harnessTypeSchema.options` for literally every known\n * harness) to widen beyond these.\n */\nexport const CODING_HARNESSES: readonly HarnessType[] = [\n 'opencode',\n 'claude-code',\n 'codex',\n 'kimi-code',\n]\n\nexport interface ProfileAxisSpec {\n /** The domain profile to sweep. Its prompt/tools/skills are held fixed; only the\n * harness and model vary. `model.default` is the fallback model. */\n base: AgentProfile\n /** Harnesses to cross. Default: {@link CODING_HARNESSES}. */\n harnesses?: readonly HarnessType[]\n /** Models to cross. Default: `[base.model.default]` — one model, i.e. today's\n * single-model behaviour, so omitting this never changes an existing run. */\n models?: readonly string[]\n /** Force every (harness, model) pair verbatim, even ones the harness can't run —\n * for deliberately testing failure modes. Default (false): SNAP instead — a\n * vendor-locked harness runs only the swept models in its family, or its native\n * default when it supports none, so no harness is dropped and none gets a\n * guaranteed-failing foreign-model cell. */\n keepIncompatible?: boolean\n}\n\n/** Model sentinel for a vendor-locked harness that supports none of the swept models:\n * it carries no provider prefix, so `harnessSupportsModel` accepts it and the harness\n * resolves it to its own native default model at runtime (e.g. kimi-code → its Kimi\n * model). Lets `expandProfileAxes` snap-instead-of-drop without a per-harness flagship\n * table that would rot as router catalogs change. */\nexport const HARNESS_NATIVE_MODEL = 'default'\n\n/**\n * Expand a base profile across the harness × model matrix into the `AgentProfile[]`\n * that `runProfileMatrix` / `selfImprove` score — the ONE place \"which harnesses ×\n * which models do we evaluate\" lives, so no product hand-rolls its own harness list\n * or column→profile mapping (the pattern that let those copies drift and silently\n * break the harness pivot).\n *\n * Each cell clones `base`, sets the canonical top-level `harness` and `model.default`,\n * and stamps `metadata.harness` + `metadata.harnessModel` for matrix grouping. Both\n * metadata fields are hash-bearing, so every cell gets a distinct `agentProfileId` row\n * and results join back by harness/model via {@link harnessAxisOf} with no\n * hand-recomputed key. A vendor-locked harness snaps to its family's swept models — or\n * its native default ({@link HARNESS_NATIVE_MODEL}) when it supports none — so every\n * requested harness runs; `keepIncompatible` forces every pair verbatim.\n *\n * Omit `harnesses`/`models` to sweep the full default set — the \"turn it on for\n * everything we care about\" switch, identical in shape whether one harness or all.\n */\nexport function expandProfileAxes(spec: ProfileAxisSpec): AgentProfile[] {\n const harnesses = spec.harnesses ?? CODING_HARNESSES\n if (harnesses.length === 0) throw new ValidationError('expandProfileAxes: no harnesses to sweep')\n const baseModel = spec.base.model?.default\n const models = spec.models ?? (baseModel ? [baseModel] : [])\n if (models.length === 0) {\n throw new ValidationError(\n 'expandProfileAxes: no models to sweep — base profile has no model.default and none were supplied',\n )\n }\n const out: AgentProfile[] = []\n const seen = new Set<string>()\n for (const harness of harnesses) {\n // A universal (router-backed) harness — opencode/pi/claudish — runs every swept\n // model. A vendor-locked harness — codex/claude-code/kimi-code — runs only the\n // swept models in its own family; when it supports NONE of them it snaps to its\n // native default (the `HARNESS_NATIVE_MODEL` sentinel it resolves at runtime)\n // rather than being dropped, so every requested harness still appears in the\n // sweep on a model it can actually run — e.g. sweeping `deepseek/x` puts opencode\n // on deepseek and kimi-code on its own Kimi model, a real head-to-head.\n // `keepIncompatible` forces every (harness, model) pair verbatim (failure-mode runs).\n const supported = spec.keepIncompatible\n ? models\n : models.filter((model) => harnessSupportsModel(harness, model))\n const effective = supported.length > 0 ? supported : [HARNESS_NATIVE_MODEL]\n for (const model of effective) {\n const profile: AgentProfile = {\n ...spec.base,\n harness,\n name: `${spec.base.name ?? 'agent'}/${harness}/${model}`,\n model: { ...spec.base.model, default: model },\n metadata: { ...(spec.base.metadata ?? {}), harness, harnessModel: model },\n }\n const id = agentProfileId(profile)\n if (seen.has(id)) continue\n seen.add(id)\n out.push(profile)\n }\n }\n if (out.length === 0) {\n // Unreachable in normal use — snapping guarantees ≥1 cell per harness — but keep a\n // fail-closed guard so a future refactor can't silently produce an empty sweep.\n throw new ValidationError(\n `expandProfileAxes: produced no profiles (harnesses=[${harnesses.join(', ')}], models=[${models.join(', ')}]).`,\n )\n }\n return out\n}\n\n/**\n * Read the (harness, model) a matrix cell ran under, off a profile or a result row's\n * profile — the join-back for a `byHarness` pivot. Returns undefined when the profile\n * wasn't produced by {@link expandProfileAxes}. Callers group `result.byProfile` by\n * this instead of recomputing an id (recomputing the wrong key is what broke the pivot\n * in the hand-rolled copies).\n */\nexport function harnessAxisOf(\n profile: Pick<AgentProfile, 'metadata'>,\n): { harness: HarnessType; model: string } | undefined {\n const m = profile.metadata as Record<string, unknown> | undefined\n const harness = m?.harness\n const model = m?.harnessModel\n if (typeof harness === 'string' && typeof model === 'string') {\n return { harness: harness as HarnessType, model }\n }\n return undefined\n}\n\n/**\n * Collision-resistant, path-safe, human-readable profile id for eval artifacts.\n * Scorecard joins still use `agentProfileHash`; this id is for run ids, matrix\n * keys, and directory names where two profiles must not collapse onto one row.\n * The suffix is the first 64 bits of the behaviour hash, enough for ordinary\n * eval matrices while keeping filenames readable.\n */\nexport function agentProfileId(profile: AgentProfile): string {\n const label = pathSafeProfileLabel(agentProfileDisplayLabel(profile)) ?? 'profile'\n return `${label}-${agentProfileHash(profile).slice(0, 16)}`\n}\n\n/**\n * Model snapshot used for `RunRecord.model`. Eval surfaces require a concrete\n * model id because run records reject bare/missing model aliases.\n */\nexport function agentProfileModelId(profile: AgentProfile): string {\n const model = profile.model?.default?.trim()\n if (!model) {\n const label = agentProfileDisplayLabel(profile) ?? 'unnamed profile'\n throw new ValidationError(\n `AgentProfile \"${label}\" has no model.default — cannot record eval run`,\n )\n }\n return model\n}\n\nfunction agentProfileDisplayLabel(profile: AgentProfile): string | undefined {\n return profile.name?.trim() || profile.version?.trim() || undefined\n}\n\nfunction pathSafeProfileLabel(label: string | undefined): string | undefined {\n const safe = label\n ?.trim()\n .replace(/[^A-Za-z0-9._-]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n return safe || undefined\n}\n\nfunction compact<T extends Record<string, unknown>>(input: T): Partial<T> {\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(input)) {\n if (value !== undefined) out[key] = value\n }\n return out as Partial<T>\n}\n\n/**\n * Deterministic behaviour identity for the canonical\n * `@tangle-network/agent-interface` AgentProfile.\n *\n * `name` and `description` are labels and do not affect the hash. Profile\n * `version`, prompt, model hints, tools, resources, hooks, modes, permissions,\n * and extensions do affect the hash. Resource array order is hash-bearing\n * because mount order can change agent behaviour. Undefined fields are treated\n * as absent; explicit `null` fields remain hash-bearing.\n */\nexport function agentProfileHash(profile: AgentProfile): string {\n const model = agentProfileModelId(profile)\n const behaviour = compact({\n ...profile,\n name: undefined,\n description: undefined,\n tags: profile.tags ? [...profile.tags].sort() : undefined,\n model: compact({ ...profile.model, default: model }),\n })\n return createHash('sha256').update(canonicalString(behaviour)).digest('hex')\n}\n","/**\n * Completion verifier — the task-completion oracle.\n *\n * Answers the only eval question that is not a proxy: did the agent actually\n * COMPLETE the task — produce every required deliverable, persisted and\n * correct — rather than describe what should be done. A fluent transcript\n * that never produces the artifact scores zero here.\n *\n * Per requirement, a two-stage check:\n * 1. Structural — a produced item (vault artifact / approved proposal /\n * tool call) of the right kind is matched against the requirement and\n * carries non-empty content. Deterministic; no LLM.\n * 2. Correctness — only if structurally present AND the matched item\n * carries content, one targeted check decides whether that item\n * actually fulfils the requirement. A hallucinated artifact fails here;\n * an absent one already failed stage 1.\n *\n * `completionRate` is satisfied / MEASURABLE requirements (unmeasured rows —\n * checker failures — are excluded from the denominator, never scored as\n * zeros). Quality dimensions are meaningless on an incomplete task — callers\n * gate on `fullyComplete` / `completionRate` before scoring quality.\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { ChatClient, ChatRequest } from './analyst/chat-client'\nimport type { Artifact } from './artifact-validator'\nimport { CostLedger, type CostLedgerHandle } from './cost-ledger'\nimport { assertServedModel, ModelSubstitutionError } from './integrity/served-model'\nimport { recoverTruncatedJson } from './json-recovery'\nimport { JudgeParseError } from './judges'\nimport {\n costReceiptFromLlm,\n costReceiptFromLlmError,\n maximumChargeForLlmRequest,\n} from './llm-client'\nimport type { RawProviderEvent, RawProviderSink } from './trace/raw-provider-sink'\nimport {\n certificationEvidenceDigest,\n type DefaultVerdict,\n type VerdictCertification,\n} from './verdict'\nimport type { CheckerIdentity, VerificationStrategySource } from './verification-strategy'\n\n/** What kind of produced state can satisfy a requirement structurally. */\nexport type SatisfiedBy = 'artifact' | 'proposal' | 'tool-call' | 'any'\n\nexport interface CompletionRequirement {\n /** Stable id from the task gold (e.g. a persona's `expected_requirements[].req_id`). */\n reqId: string\n /** Human-readable description of the required deliverable. */\n title: string\n /** Optional kind/category hint, matched against a produced item's kind. */\n category?: string\n /** What produced state satisfies this requirement. Defaults to 'any'. */\n satisfiedBy?: SatisfiedBy\n}\n\nexport interface TaskGold {\n taskId: string\n requirements: CompletionRequirement[]\n}\n\nexport interface ProducedProposal {\n id: string\n title: string\n status: 'pending' | 'approved' | 'rejected'\n /** Optional persisted body — when present, enables a correctness check. */\n content?: string\n}\n\n/** Everything observable about what a run actually produced. */\nexport interface ProducedState {\n /** Persisted vault artifacts. Reuses the shared `Artifact` shape. */\n artifacts: Artifact[]\n /** Proposals / filings the agent created. */\n proposals: ProducedProposal[]\n /** Names of tools the agent invoked. */\n toolCalls: string[]\n}\n\nexport interface RequirementCheck {\n reqId: string\n title: string\n /** A produced item of the right kind matched the requirement, non-empty. */\n structurallyPresent: boolean\n /**\n * Whether the matched item actually fulfils the requirement. `null` when\n * not structurally present, when the matched item carries no content\n * to assess, or when the correctness check itself failed (`unmeasured`).\n */\n correct: boolean | null\n /** structurallyPresent && !unmeasured && correct !== false. */\n satisfied: boolean\n /**\n * Set when the correctness check itself errored (LLM call failure or an\n * unparseable response after retry). The requirement's fulfilment is\n * UNKNOWN — `correct` stays null, `satisfied` is false, and\n * `completionVerdict` excludes the row from `completionRate`'s\n * denominator. Never folded into a zero: a synthetic zero is\n * indistinguishable from a real failure (see `JudgeParseError`).\n */\n unmeasured?: true\n /** Why the correctness check could not be measured (present iff `unmeasured`). */\n unmeasuredReason?: string\n /** Human-readable evidence for the verdict. */\n evidence: string[]\n}\n\n/** Extends the substrate verdict spine: `valid` = `fullyComplete` and\n * `score` = `completionRate` — derived in `completionVerdict()`, the one\n * place those equalities hold by construction. */\nexport interface CompletionVerdict extends DefaultVerdict {\n taskId: string\n requirements: RequirementCheck[]\n /** satisfied / MEASURABLE requirements (unmeasured rows leave the denominator). */\n completionRate: number\n /** Every measurable requirement satisfied (false when anything is unmeasured). */\n fullyComplete: boolean\n /** Requirements whose correctness check errored — reported, never scored as zero. */\n unmeasuredCount: number\n}\n\n/**\n * Construct a `CompletionVerdict` from the per-requirement checks, deriving\n * `completionRate` / `fullyComplete` and the spine fields (`valid` =\n * `fullyComplete`, `score` = `completionRate`) in one place. Throws on zero\n * requirements — a verdict over nothing is a misconfiguration, mirroring\n * `verifyCompletion`'s gold-spec guard.\n */\nexport function completionVerdict(input: {\n taskId: string\n requirements: RequirementCheck[]\n /** What certified the correctness stage, when anything did. Omitted =\n * an uncertified verdict — the honest default for a bare checker. */\n certification?: VerdictCertification\n}): CompletionVerdict {\n if (input.requirements.length === 0) {\n throw new Error(\n `completionVerdict: task '${input.taskId}' has no requirement checks — nothing to derive a verdict from`,\n )\n }\n const measurable = input.requirements.filter((r) => !r.unmeasured)\n const unmeasuredCount = input.requirements.length - measurable.length\n if (measurable.length === 0) {\n // Every check errored: this is an infrastructure failure, not a scored\n // run. A 0-rate verdict here would be a fabricated measurement.\n throw new Error(\n `completionVerdict: task '${input.taskId}' has no measurable requirements — all ${input.requirements.length} correctness checks failed (${input.requirements[0]?.unmeasuredReason ?? 'unknown reason'})`,\n )\n }\n const satisfiedCount = measurable.filter((r) => r.satisfied).length\n const completionRate = satisfiedCount / measurable.length\n // A run with unmeasured rows can still report a rate over what WAS\n // measured, but must not claim full completion.\n const fullyComplete = unmeasuredCount === 0 && satisfiedCount === measurable.length\n return {\n taskId: input.taskId,\n requirements: input.requirements,\n completionRate,\n fullyComplete,\n unmeasuredCount,\n valid: fullyComplete,\n score: completionRate,\n ...(input.certification === undefined ? {} : { certification: input.certification }),\n }\n}\n\n/**\n * What a correctness checker declares about itself so the completion\n * verdict can carry a certification: which strategy member it discharges,\n * its exact identity, and the steps its answers rest on unverified.\n */\nexport interface CorrectnessCheckerAttestation {\n strategy: VerificationStrategySource\n checker: CheckerIdentity\n assumptions: string[]\n}\n\n/**\n * Decides whether a produced item's content actually fulfils a requirement.\n * Injected so the structural verifier stays pure and unit-testable; the\n * production implementation is `createLlmCorrectnessChecker`.\n *\n * `attestation` is optional metadata on the function value: a checker that\n * carries one yields certified completion verdicts; a bare function yields\n * the same verdict uncertified. A plain arrow function remains a valid\n * checker.\n */\nexport interface CorrectnessChecker {\n (\n requirement: CompletionRequirement,\n content: string,\n ): Promise<{ correct: boolean; reason: string }>\n attestation?: CorrectnessCheckerAttestation\n}\n\nconst STOPWORDS = new Set([\n 'the',\n 'a',\n 'an',\n 'of',\n 'for',\n 'and',\n 'or',\n 'to',\n 'in',\n 'on',\n 'with',\n 'by',\n])\n\n// Deliverable-FORM vocabulary — words that name the SHAPE of an output, not its\n// domain content. A correct \"swap-comparison view persisted as a ui/** artifact\"\n// is an OpenUI JSON whose body says nothing about \"artifact\" / \"persisted\" /\n// \"view\"; the discriminative tokens are the domain nouns (swap, comparison).\n// Stripped from the REQUIREMENT side of structural recall so a deliverable is\n// matched on what it IS about, not on the boilerplate describing its form. The\n// correctness checker strips the same class via TITLE_STOPWORDS. Anti-game holds:\n// the distinctive domain tokens remain, so an off-topic item still fails.\nconst REQUIREMENT_FORM_STOPWORDS = new Set([\n 'generated',\n 'generate',\n 'view',\n 'render',\n 'rendered',\n 'persisted',\n 'persist',\n 'artifact',\n 'file',\n 'document',\n 'note',\n 'proposal',\n 'deliverable',\n 'output',\n 'created',\n 'create',\n 'produce',\n 'produced',\n 'flag',\n])\n\nconst MATCH_THRESHOLD = 0.5\nconst MIN_CONTENT_CHARS = 50\n\nfunction tokens(s: string, extraStop?: Set<string>): Set<string> {\n return new Set(\n s\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter((t) => t.length > 1 && !STOPWORDS.has(t) && !extraStop?.has(t)),\n )\n}\n\n/**\n * Recall of the requirement's tokens within a candidate's identifying text.\n * Recall, not Jaccard — a candidate's path/id legitimately carries extra\n * tokens the requirement does not name. The requirement side drops\n * deliverable-FORM vocabulary so recall keys on the distinctive domain tokens.\n */\nfunction tokenRecall(requirementText: string, candidateText: string): number {\n const req = tokens(requirementText, REQUIREMENT_FORM_STOPWORDS)\n if (req.size === 0) return 0\n const cand = tokens(candidateText)\n let hit = 0\n for (const t of req) if (cand.has(t)) hit++\n return hit / req.size\n}\n\ninterface Candidate {\n reqIndex: number\n /** Unique key for a produced item — each item satisfies at most one requirement. */\n itemKey: string\n score: number\n evidence: string\n /** Content to correctness-check, or null when the matched item has none. */\n content: string | null\n}\n\nfunction artifactCandidates(\n req: CompletionRequirement,\n reqIndex: number,\n artifacts: Artifact[],\n): Candidate[] {\n const reqText = `${req.title} ${req.category ?? ''}`\n const out: Candidate[] = []\n artifacts.forEach((a, i) => {\n if ((a.content ?? '').trim().length < MIN_CONTENT_CHARS) return\n // Match against the artifact CONTENT too, not just its path + kind — a\n // generated view / note whose path is generic still satisfies a requirement\n // when its body covers it (e.g. an OpenUI comparison grounded in the on-file\n // figures). Bounded slice keeps the recall text cheap; MATCH_THRESHOLD holds.\n let score = tokenRecall(\n reqText,\n `${a.path ?? ''} ${a.kind} ${(a.content ?? '').slice(0, 4000)}`,\n )\n if (req.category && a.kind && req.category.toLowerCase() === a.kind.toLowerCase()) {\n score = Math.max(score, 1)\n }\n if (score < MATCH_THRESHOLD) return\n out.push({\n reqIndex,\n itemKey: `artifact:${i}`,\n score,\n evidence: `artifact '${a.path ?? a.kind}' matched (token recall ${score.toFixed(2)})`,\n content: a.content ?? null,\n })\n })\n return out\n}\n\nfunction proposalCandidates(\n req: CompletionRequirement,\n reqIndex: number,\n proposals: ProducedProposal[],\n): Candidate[] {\n const reqText = `${req.title} ${req.category ?? ''}`\n const out: Candidate[] = []\n for (const p of proposals) {\n // Pending or rejected work is not a completed deliverable.\n if (p.status !== 'approved') continue\n // A proposal needs an assessable BODY to be a deliverable. A bare title is\n // not completion: correctness cannot be judged on it, so a title-only match\n // would auto-pass the oracle (structurallyPresent && correct===null →\n // satisfied) with no verifiable content. Tool calls are the only\n // legitimately content-less deliverable (`toolCallCandidates`).\n const body = (p.content ?? '').trim()\n if (body.length < MIN_CONTENT_CHARS) continue\n // Match against the body as well as the (often short) title — a refusal /\n // flag / analysis proposal whose title is a label still satisfies a\n // descriptively-worded requirement when its content covers it. MATCH_THRESHOLD\n // + the requirement's distinctive tokens keep an off-topic proposal out;\n // correctness (a SEMANTIC checker, NOT this lexical pass) then judges\n // polarity/fulfilment, so a negation that merely contains the tokens fails.\n // Structural and correctness must use different evidence or the two-stage\n // check collapses to one lexical gate.\n const score = tokenRecall(reqText, `${p.title} ${body}`)\n if (score < MATCH_THRESHOLD) continue\n out.push({\n reqIndex,\n itemKey: `proposal:${p.id}`,\n score,\n evidence: `approved proposal '${p.title}' matched (token recall ${score.toFixed(2)})`,\n content: body,\n })\n }\n return out\n}\n\nfunction toolCallCandidates(\n req: CompletionRequirement,\n reqIndex: number,\n toolCalls: string[],\n): Candidate[] {\n const out: Candidate[] = []\n toolCalls.forEach((name, i) => {\n const score = tokenRecall(req.title, name)\n if (score < MATCH_THRESHOLD) return\n out.push({\n reqIndex,\n itemKey: `tool:${i}`,\n score,\n evidence: `tool call '${name}' matched (token recall ${score.toFixed(2)})`,\n content: null,\n })\n })\n return out\n}\n\n/**\n * Verify whether a run completed the task. `checkCorrectness` is injected —\n * `createLlmCorrectnessChecker` for production, a deterministic stub in tests.\n *\n * Throws on a gold spec with no requirements: an eval task that requires\n * nothing is a misconfiguration, not a vacuously-complete task.\n */\nexport async function verifyCompletion(\n gold: TaskGold,\n state: ProducedState,\n checkCorrectness: CorrectnessChecker,\n): Promise<CompletionVerdict> {\n if (gold.requirements.length === 0) {\n throw new Error(\n `verifyCompletion: task '${gold.taskId}' has no requirements — malformed gold spec`,\n )\n }\n\n // Collect every above-threshold (requirement, produced-item) candidate, then\n // assign greedily by descending score: each requirement and each produced\n // item is used at most once. One deliverable fulfils one requirement.\n const candidates: Candidate[] = []\n gold.requirements.forEach((req, i) => {\n const by = req.satisfiedBy ?? 'any'\n if (by === 'artifact' || by === 'any') {\n candidates.push(...artifactCandidates(req, i, state.artifacts))\n }\n if (by === 'proposal' || by === 'any') {\n candidates.push(...proposalCandidates(req, i, state.proposals))\n }\n if (by === 'tool-call' || by === 'any') {\n candidates.push(...toolCallCandidates(req, i, state.toolCalls))\n }\n })\n candidates.sort((a, b) => b.score - a.score)\n\n const assigned = new Map<number, Candidate>()\n const itemTaken = new Set<string>()\n for (const c of candidates) {\n if (assigned.has(c.reqIndex) || itemTaken.has(c.itemKey)) continue\n assigned.set(c.reqIndex, c)\n itemTaken.add(c.itemKey)\n }\n\n const requirements: RequirementCheck[] = []\n for (let i = 0; i < gold.requirements.length; i++) {\n const req = gold.requirements[i]!\n const match = assigned.get(i)\n const evidence: string[] = []\n let correct: boolean | null = null\n let unmeasuredReason: string | undefined\n\n if (match) {\n evidence.push(match.evidence)\n if (match.content !== null) {\n try {\n const r = await checkCorrectness(req, match.content)\n correct = r.correct\n evidence.push(`correctness: ${r.correct ? 'pass' : 'fail'} — ${r.reason}`)\n } catch (err) {\n // The CHECKER failed, not the requirement. Recording this as a\n // zero would fabricate a model failure out of an infrastructure\n // one; the requirement is unmeasured and leaves the denominator.\n unmeasuredReason =\n err instanceof JudgeParseError\n ? `checker response unparseable after retry: ${err.raw.slice(0, 200)}`\n : `checker call failed: ${err instanceof Error ? err.message : String(err)}`\n evidence.push(`correctness: UNMEASURED — ${unmeasuredReason}`)\n }\n } else {\n evidence.push('correctness: not assessed — matched item carries no content')\n }\n } else {\n const by = req.satisfiedBy ?? 'any'\n const kind = by === 'any' ? 'artifact/proposal/tool-call' : by\n evidence.push(`no produced ${kind} matched this requirement`)\n }\n\n const structurallyPresent = match !== undefined\n const unmeasured = unmeasuredReason !== undefined\n const satisfied = structurallyPresent && !unmeasured && correct !== false\n requirements.push({\n reqId: req.reqId,\n title: req.title,\n structurallyPresent,\n correct,\n satisfied,\n ...(unmeasured ? { unmeasured: true as const, unmeasuredReason } : {}),\n evidence,\n })\n }\n\n // The verdict blends the deterministic structural stage with the injected\n // correctness stage, so the certification names the checker's own member\n // and carries the structural stage's lexical nature as an assumption. A\n // checker without an attestation yields an uncertified verdict.\n const attestation = checkCorrectness.attestation\n const certification: VerdictCertification | undefined = attestation\n ? {\n strategy: attestation.strategy,\n checker: attestation.checker,\n assumptions: [\n 'structural matching is lexical token recall over produced items — the correctness stage only sees items it matched',\n ...attestation.assumptions,\n ],\n evidenceDigest: certificationEvidenceDigest({ taskId: gold.taskId, requirements }),\n }\n : undefined\n\n return completionVerdict({\n taskId: gold.taskId,\n requirements,\n ...(certification === undefined ? {} : { certification }),\n })\n}\n\nexport interface LlmCorrectnessCheckerOpts {\n model?: string\n /** Optional ledger for direct use. */\n costLedger?: CostLedgerHandle\n costPhase?: string\n costTags?: Record<string, string>\n signal?: AbortSignal\n /** Max chars of artifact content sent to the checker. */\n maxContentChars?: number\n /**\n * Checker LLM calls per requirement before giving up (parse failures and\n * call errors both consume attempts). The failure then surfaces as an\n * `unmeasured` requirement, never a zero.\n */\n maxAttempts?: number\n /**\n * Forensic capture of every checker request/response/error — without it a\n * checker failure is unauditable (the agent-turn raws never contain the\n * checker's own calls). Same sink contract as `LlmClient`.\n */\n rawSink?: RawProviderSink\n}\n\n/**\n * Parse the correctness checker's model response. Tolerates a response\n * truncated mid-JSON (max_tokens cap) by auto-closing the prefix — the\n * verdict boolean usually lands in the first few tokens, so a recovered\n * prefix with a boolean `correct` is a real measurement, not a guess.\n * Fails loud (JudgeParseError) when no boolean verdict is recoverable.\n */\nexport function parseCorrectnessResponse(raw: string): { correct: boolean; reason: string } {\n const readVerdict = (candidate: unknown): { correct: boolean; reason: string } | null => {\n if (candidate === null || typeof candidate !== 'object') return null\n const { correct, reason } = candidate as { correct?: unknown; reason?: unknown }\n if (typeof correct !== 'boolean') return null\n return { correct, reason: typeof reason === 'string' ? reason : '' }\n }\n\n const match = raw.match(/\\{[\\s\\S]*\\}/)\n if (match) {\n try {\n const strict = readVerdict(JSON.parse(match[0]))\n if (strict) return strict\n } catch {\n // fall through to truncation recovery\n }\n }\n // The strict path needs a closing `}`; a cap-hit response has none. Take\n // everything from the first `{` and auto-close it.\n const start = raw.indexOf('{')\n if (start !== -1) {\n const recovered = readVerdict(recoverTruncatedJson(raw.slice(start)))\n if (recovered) return recovered\n }\n throw new JudgeParseError('correctness-checker', raw)\n}\n\n/**\n * Production `CorrectnessChecker` — one LLM call per matched artifact,\n * deterministic (temperature 0), structured JSON out. Judges fulfilment\n * only: a plan, a gesture, or a description of what should be done does not\n * fulfil a requirement — the artifact must BE the deliverable.\n */\nexport function createLlmCorrectnessChecker(\n chat: ChatClient,\n opts: LlmCorrectnessCheckerOpts = {},\n): CorrectnessChecker {\n const model = opts.model ?? 'claude-sonnet-4-6'\n const maxContentChars = opts.maxContentChars ?? 8000\n const maxAttempts = opts.maxAttempts ?? 2\n const costLedger = opts.costLedger ?? new CostLedger()\n const sink = opts.rawSink\n const record = async (event: RawProviderEvent): Promise<void> => {\n // Forensic capture is best-effort; the verdict is the system of record.\n try {\n await sink?.record(event)\n } catch {\n // Intentionally swallowed.\n }\n }\n const checker: CorrectnessChecker = async (requirement, content) => {\n const request = {\n model,\n messages: [\n {\n role: 'system' as const,\n content:\n 'You verify whether a produced work artifact actually fulfils a stated requirement. Judge fulfilment only — is the deliverable substantively present and on-point — not polish. A plan to do it later, a vague gesture, or a description of what should be done does NOT fulfil a requirement; the artifact must BE the deliverable. Respond with a single JSON object: {\"correct\": boolean, \"reason\": string (<= 30 words)}.',\n },\n {\n role: 'user' as const,\n content: `Requirement: ${requirement.title}\\n${\n requirement.category ? `Category: ${requirement.category}\\n` : ''\n }\\nProduced artifact:\\n${content.slice(0, maxContentChars)}`,\n },\n ],\n temperature: 0,\n maxTokens: 200,\n } satisfies ChatRequest\n let lastErr: unknown\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n const started = Date.now()\n await record({\n eventId: randomUUID(),\n provider: chat.transport,\n model,\n endpoint: '/chat',\n baseUrl: '',\n attemptIndex: attempt,\n direction: 'request',\n timestamp: started,\n requestBody: request,\n redactedFields: [],\n })\n try {\n const paid = await costLedger.runPaidCall({\n channel: 'verifier',\n phase: opts.costPhase ?? 'completion.correctness',\n actor: 'correctness-checker',\n model,\n maximumCharge:\n chat.maximumAttempts === undefined\n ? undefined\n : maximumChargeForLlmRequest(request, { maximumAttempts: chat.maximumAttempts }),\n tags: {\n ...opts.costTags,\n requirementId: requirement.reqId,\n attempt: String(attempt),\n },\n signal: opts.signal,\n execute: (signal, callId) => chat.chat(request, { signal, idempotencyKey: callId }),\n receipt: costReceiptFromLlm,\n receiptFromError: costReceiptFromLlmError,\n })\n if (!paid.succeeded) throw paid.error\n const resp = paid.value\n // Hold the transport to its own word: a verdict produced by a model\n // other than the one requested is not this model's verdict. A\n // transport that echoes no id cannot be made to prove identity here —\n // callers needing that proof enable it at the client\n // (`LlmClientOptions.assertServedModel`) or gate on `assertModelsServed`.\n assertServedModel(model, resp.servedModel, {\n allowUnreported: true,\n context: `correctness checker for requirement ${requirement.reqId}`,\n })\n const raw = resp.content\n await record({\n eventId: randomUUID(),\n provider: chat.transport,\n model,\n endpoint: '/chat',\n baseUrl: '',\n attemptIndex: attempt,\n direction: 'response',\n timestamp: Date.now(),\n durationMs: Date.now() - started,\n responseBody: resp,\n redactedFields: [],\n })\n return parseCorrectnessResponse(raw)\n } catch (err) {\n lastErr = err\n await record({\n eventId: randomUUID(),\n provider: chat.transport,\n model,\n endpoint: '/chat',\n baseUrl: '',\n attemptIndex: attempt,\n direction: 'error',\n timestamp: Date.now(),\n durationMs: Date.now() - started,\n errorMessage: err instanceof Error ? err.message : String(err),\n redactedFields: [],\n })\n // Substitution is a verdict about identity, not a transient fault:\n // another attempt reaches the same wrong model and spends more tokens.\n if (err instanceof ModelSubstitutionError) throw err\n }\n }\n throw lastErr instanceof Error ? lastErr : new Error(String(lastErr))\n }\n checker.attestation = {\n strategy: 'judge',\n checker: { name: 'llm-correctness-checker', version: model },\n assumptions: [\n 'served-model identity is accepted when the transport reports none (assertServedModel allowUnreported)',\n ],\n }\n return checker\n}\n","/**\n * Produced-state extraction — normalize a run's runtime event stream into the\n * typed `ProducedState` the completion oracle consumes.\n *\n * `ProducedState` answers \"what did the agent actually produce\" — vault\n * artifacts, proposals, tool calls. The runtime emits these as a stream of\n * events; this module is the single normalization point from that stream to\n * the shape `verifyCompletion` expects.\n *\n * Input is structurally typed (`RuntimeEventLike`) so this module does not\n * depend on agent-runtime — agent-runtime's `RuntimeStreamEvent` satisfies it\n * structurally. The `content` on `ArtifactEventLike` and the whole\n * `proposal_created` variant are the runtime-side enrichments this contract\n * requires; the runtime emits them, this module consumes them.\n */\n\nimport type { Artifact } from './artifact-validator'\nimport type { ProducedProposal, ProducedState } from './completion-verifier'\n\n/** A tool the agent invoked. */\nexport interface ToolCallEventLike {\n type: 'tool_call'\n toolName: string\n}\n\n/**\n * An artifact the agent produced. `content` is the enriched field — the\n * runtime's base `artifact` event carries only metadata; the completion\n * oracle needs the body to verify the deliverable, so the runtime emits it.\n */\nexport interface ArtifactEventLike {\n type: 'artifact'\n artifactId: string\n name?: string\n mimeType?: string\n uri?: string\n content?: string\n}\n\n/** A proposal / filing the agent created. */\nexport interface ProposalEventLike {\n type: 'proposal_created'\n proposalId: string\n title: string\n status?: 'pending' | 'approved' | 'rejected'\n // body of the proposal (e.g. a submit_proposal `description`). When present,\n // the completion oracle correctness-checks it like artifact content; absent,\n // the proposal is graded presence-only.\n content?: string\n}\n\n/**\n * The subset of runtime stream events `extractProducedState` consumes.\n * agent-runtime's full `RuntimeStreamEvent` union satisfies this structurally;\n * the `{ type: string }` catch-all keeps the input permissive so callers can\n * pass the whole unfiltered telemetry stream — unrecognized events are skipped.\n */\nexport type RuntimeEventLike =\n | ToolCallEventLike\n | ArtifactEventLike\n | ProposalEventLike\n | { type: string }\n\nfunction artifactKind(mimeType: string | undefined): string {\n if (!mimeType) return 'file'\n if (mimeType.includes('json')) return 'json'\n if (mimeType.startsWith('text/')) return 'text'\n return 'file'\n}\n\n/**\n * Normalize an emission-ordered stream into its latest observed produced state.\n * Artifacts are keyed by their exact output path (name, then URI, then id);\n * proposals by id. Later observations replace earlier ones, including missing\n * content or a rejected status. An obsolete version cannot prove completion.\n * Distinct paths remain distinct; no path normalization or aliasing is inferred.\n * Results retain first-seen identity order. Tool names describe invocation,\n * not success, and are deduplicated in first-seen order.\n *\n * An artifact without observed content yields empty content, which the\n * completion oracle rejects. This projection does not verify external effects,\n * recover dropped events, or establish freshness beyond the supplied stream.\n */\nexport function extractProducedState(events: readonly RuntimeEventLike[]): ProducedState {\n const artifacts = new Map<string, Artifact>()\n const proposals = new Map<string, ProducedProposal>()\n const toolCalls: string[] = []\n const seenTools = new Set<string>()\n\n for (const ev of events) {\n if (ev.type === 'tool_call') {\n const name = (ev as ToolCallEventLike).toolName\n if (name && !seenTools.has(name)) {\n seenTools.add(name)\n toolCalls.push(name)\n }\n } else if (ev.type === 'artifact') {\n const a = ev as ArtifactEventLike\n const path = a.name ?? a.uri ?? a.artifactId\n artifacts.set(path, {\n kind: artifactKind(a.mimeType),\n path,\n content: a.content ?? '',\n })\n } else if (ev.type === 'proposal_created') {\n const p = ev as ProposalEventLike\n proposals.set(p.proposalId, {\n id: p.proposalId,\n title: p.title,\n status: p.status ?? 'pending',\n ...(p.content !== undefined ? { content: p.content } : {}),\n })\n }\n }\n\n return { artifacts: [...artifacts.values()], proposals: [...proposals.values()], toolCalls }\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import { C as verifyAgentProfileCell, S as validateAgentProfileCell, _ as buildAgentInterfaceProfileCell, a as AgentProfileCellSchemaVersion, b as requireAgentProfileCell, c as AgentProfileHarness, d as AgentProfileKind, f as AgentProfileSource, g as assertRunAgentProfileCell, h as agentProfileCellKey, i as AgentProfileCellInput, l as AgentProfileJson, m as agentProfileCellHashMaterial, n as AgentInterfaceProfileLike, o as AgentProfileCellValidationError, p as AgentProfileSourceInput, r as AgentProfileCell, s as AgentProfileDimensionValue, t as AGENT_PROFILE_KINDS, u as AgentProfileJsonObject, v as buildAgentProfileCell, x as toAgentProfileJson, y as groupRunsByAgentProfileCell } from "./agent-profile-cell-BHcB-mZX.js";
2
- export { AGENT_PROFILE_KINDS, AgentInterfaceProfileLike, AgentProfileCell, AgentProfileCellInput, AgentProfileCellSchemaVersion, AgentProfileCellValidationError, AgentProfileDimensionValue, AgentProfileHarness, AgentProfileJson, AgentProfileJsonObject, AgentProfileKind, AgentProfileSource, AgentProfileSourceInput, agentProfileCellHashMaterial, agentProfileCellKey, assertRunAgentProfileCell, buildAgentInterfaceProfileCell, buildAgentProfileCell, groupRunsByAgentProfileCell, requireAgentProfileCell, toAgentProfileJson, validateAgentProfileCell, verifyAgentProfileCell };
1
+ import { S as verifyAgentProfileCell, _ as buildAgentProfileCell, a as AgentProfileCellSchemaVersion, b as toAgentProfileJson, c as AgentProfileHarness, d as AgentProfileKind, f as AgentProfileSource, g as buildAgentInterfaceProfileCell, h as assertRunAgentProfileCell, i as AgentProfileCellInput, l as AgentProfileJson, m as agentProfileCellKey, n as AgentInterfaceProfileLike, o as AgentProfileCellValidationError, p as AgentProfileSourceInput, r as AgentProfileCell, s as AgentProfileDimensionValue, t as AGENT_PROFILE_KINDS, u as AgentProfileJsonObject, v as groupRunsByAgentProfileCell, x as validateAgentProfileCell, y as requireAgentProfileCell } from "./agent-profile-cell-BNp-Js9F.js";
2
+ export { AGENT_PROFILE_KINDS, AgentInterfaceProfileLike, AgentProfileCell, AgentProfileCellInput, AgentProfileCellSchemaVersion, AgentProfileCellValidationError, AgentProfileDimensionValue, AgentProfileHarness, AgentProfileJson, AgentProfileJsonObject, AgentProfileKind, AgentProfileSource, AgentProfileSourceInput, agentProfileCellKey, assertRunAgentProfileCell, buildAgentInterfaceProfileCell, buildAgentProfileCell, groupRunsByAgentProfileCell, requireAgentProfileCell, toAgentProfileJson, validateAgentProfileCell, verifyAgentProfileCell };
@@ -1,2 +1,2 @@
1
- import { a as assertRunAgentProfileCell, c as groupRunsByAgentProfileCell, d as validateAgentProfileCell, f as verifyAgentProfileCell, i as agentProfileCellKey, l as requireAgentProfileCell, n as AgentProfileCellValidationError, o as buildAgentInterfaceProfileCell, r as agentProfileCellHashMaterial, s as buildAgentProfileCell, t as AGENT_PROFILE_KINDS, u as toAgentProfileJson } from "./agent-profile-cell-DcFW4vOc.js";
2
- export { AGENT_PROFILE_KINDS, AgentProfileCellValidationError, agentProfileCellHashMaterial, agentProfileCellKey, assertRunAgentProfileCell, buildAgentInterfaceProfileCell, buildAgentProfileCell, groupRunsByAgentProfileCell, requireAgentProfileCell, toAgentProfileJson, validateAgentProfileCell, verifyAgentProfileCell };
1
+ import { a as buildAgentInterfaceProfileCell, c as requireAgentProfileCell, d as verifyAgentProfileCell, i as assertRunAgentProfileCell, l as toAgentProfileJson, n as AgentProfileCellValidationError, o as buildAgentProfileCell, r as agentProfileCellKey, s as groupRunsByAgentProfileCell, t as AGENT_PROFILE_KINDS, u as validateAgentProfileCell } from "./agent-profile-cell-BYc0bxsL.js";
2
+ export { AGENT_PROFILE_KINDS, AgentProfileCellValidationError, agentProfileCellKey, assertRunAgentProfileCell, buildAgentInterfaceProfileCell, buildAgentProfileCell, groupRunsByAgentProfileCell, requireAgentProfileCell, toAgentProfileJson, validateAgentProfileCell, verifyAgentProfileCell };
@@ -1,6 +1,6 @@
1
1
  import { r as pairedBootstrap } from "./paired-tests-jbwPdSz5.js";
2
- import { t as decidePairedPromotion } from "./paired-promotion-decision-2deJ7sDX.js";
3
- import { o as pairHoldout, r as detectScale } from "./statistical-heldout-BP6iEJvR.js";
2
+ import { t as decidePairedPromotion } from "./paired-promotion-decision-BN6IQTiu.js";
3
+ import { o as pairHoldout, r as detectScale } from "./statistical-heldout-1Gbmex7v.js";
4
4
  //#region src/campaign/gates/promotion-policy.ts
5
5
  /**
6
6
  * Build paired evidence for each objective and apply a promotion policy.
@@ -171,4 +171,4 @@ function paretoSignificanceGate(options) {
171
171
  //#endregion
172
172
  export { paretoPolicy as n, paretoSignificanceGate as r, buildEvidenceVector as t };
173
173
 
174
- //# sourceMappingURL=promotion-policy-CCmyEH_c.js.map
174
+ //# sourceMappingURL=promotion-policy-CWW35UWq.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"promotion-policy-CCmyEH_c.js","names":[],"sources":["../src/campaign/gates/promotion-policy.ts"],"sourcesContent":["/**\n * Build paired evidence for each objective and apply a promotion policy.\n * The default policy requires at least one gain and every regression floor to\n * clear. A floor can fail because a larger loss remains plausible; that does\n * not demonstrate an observed regression. Missing evidence stays unresolved.\n * Confidence intervals apply to each axis, without a multiplicity adjustment.\n *\n * Cost and latency remain aggregate constraints because GateContext does not\n * supply paired observations for them. Compose a budget gate when needed.\n */\n\nimport {\n decidePairedPromotion,\n type PairedDecisionMethod,\n type PairedDecisionStatistic,\n type PairedMcNemarEvidence,\n} from '../../paired-promotion-decision'\nimport type { Direction } from '../../pareto'\nimport {\n DECISION_PAIRED_DELTA_STATISTIC,\n type PairedBootstrapResult,\n pairedBootstrap,\n} from '../../statistics'\nimport type { Gate, GateContext, GateDecision, GateResult, JudgeScore, Scenario } from '../types'\nimport { detectScale, pairHoldout } from './statistical-heldout'\n\n/** Where an objective's per-cell scalar comes from. `composite` reads the\n * judge's composite; `dimension` reads a named per-dimension score. */\nexport type ObjectiveSource = { kind: 'composite' } | { kind: 'dimension'; dimension: string }\n\nexport interface PromotionObjective {\n /** Stable label used in reports + `contributingGates`. */\n name: string\n source: ObjectiveSource\n /** 'maximize' (quality dims) or 'minimize' (error/risk/length dims). Orients\n * the paired delta so a positive bootstrap always means \"candidate better\". */\n direction: Direction\n /** Declared binary support {0, binaryScale}, including zero-only observations.\n * Must be finite and positive; paired cell scores must be 0 or this scale.\n * Uses the risk-difference mean and rejects the 'median' statistic. */\n binaryScale?: number\n /** The good-direction paired-delta CI lower bound must EXCEED this to count\n * as a significant gain on this axis. Interpreted in the judge's native\n * scale. Default 0 (⇒ \"confidently better\"). */\n gainThreshold?: number\n /** A floor breach (regression) is declared when the good-direction CI lower\n * bound is below −floorTolerance, or when the exact small-sample test proves\n * a drop past it. Defaults to 0.05 times the declared binary scale, or\n * auto-scales off observed magnitudes (0.05 on [0,1], 5 on 0-100). */\n floorTolerance?: number\n}\n\n/** Per-axis verdict from the shared paired decision rule.\n * 'regressed' includes uncertainty that prevents clearing the regression floor. */\nexport type AxisVerdict = 'improved' | 'regressed' | 'flat' | 'few_runs' | 'indeterminate'\n\nexport interface AxisEvidence {\n name: string\n source: ObjectiveSource\n direction: Direction\n /** Paired bootstrap on the GOOD-DIRECTION delta (oriented by `direction`):\n * a positive value means the candidate is better on this axis.\n *\n * DIAGNOSTIC on a pass/fail axis: there the verdict is decided on Tango's\n * score interval instead, because a percentile bootstrap over a three-atom\n * delta lattice is not a valid interval at the nonzero margin `floorTolerance`\n * and `gainThreshold` create. `ci` carries the interval that decided. */\n bootstrap: PairedBootstrapResult\n /** Which paired statistic `bootstrap.low`/`.high` bracket. `'mean'` unless the\n * caller asked for the median — on a pass/fail axis the median and its whole\n * CI are pinned at 0 by tie domination and can see neither a gain nor a\n * regression. `bootstrap.median` still carries the median point estimate. */\n bootstrapStatistic: 'median' | 'mean'\n /** The interval the axis verdict was actually decided on, good-direction and\n * in the axis's native units. */\n ci: { low: number; high: number }\n /** Which estimator produced `ci`. */\n decisionStatistic: PairedDecisionStatistic\n /** McNemar's exact evidence on a pass/fail axis; null otherwise. */\n mcnemar: PairedMcNemarEvidence | null\n /** `ci` has zero width or non-finite bounds. It cannot establish a gain or\n * clear a regression floor, regardless of the point estimate. */\n indeterminate: boolean\n /** Paired observations contributing to this axis. */\n n: number\n minimumRequired: number\n decisionMethod: PairedDecisionMethod\n gainThreshold: number\n floorTolerance: number\n verdict: AxisVerdict\n}\n\nexport interface EvidenceVector {\n /** One entry per objective — NOTHING averaged across axes. */\n axes: AxisEvidence[]\n /** Smallest paired n across axes that produced observations — the binding\n * evidence-sufficiency constraint. 0 when no axis produced observations. */\n minN: number\n /** Aggregate per-side cost from the gate context (a constraint input, not a\n * CI axis — see the module header). */\n cost: { candidate: number; baseline: number }\n}\n\n/** A promotion strategy: a pure function from the evidence vector to a verdict.\n * Many policies can run over the same `EvidenceVector` and disagree — that's\n * the point (competing strategies, shared evidence). */\nexport type PromotionPolicy = (ev: EvidenceVector) => GateResult\n\nexport interface BuildEvidenceVectorOptions {\n /** Minimum paired observations before an axis can claim significance; below\n * it the axis is `few_runs`. The exact small-sample test may require more\n * observations at the selected confidence. */\n minProductiveRuns?: number\n /** Confidence level for every axis bootstrap. Default 0.95. */\n confidence?: number\n /** Bootstrap resamples. Default 2000. */\n resamples?: number\n /** Fixed bootstrap seed for a deterministic, reproducible verdict. Default 1337. */\n seed?: number\n /** Paired statistic every axis CI is computed on. Default `'mean'` — see\n * {@link DECISION_PAIRED_DELTA_STATISTIC} for why the median is not. */\n statistic?: 'mean' | 'median'\n}\n\n/**\n * The Evidence Bus. For each objective, pair candidate vs baseline by full\n * cellId and bootstrap a CI on the good-direction paired delta. Reuses the\n * exact `pairHoldout` + `pairedBootstrap` machinery the held-out gate uses, so\n * a single source of truth governs pairing granularity + scale handling.\n */\nexport function buildEvidenceVector<TArtifact, TScenario extends Scenario>(\n ctx: GateContext<TArtifact, TScenario>,\n objectives: PromotionObjective[],\n opts: BuildEvidenceVectorOptions = {},\n): EvidenceVector {\n if (objectives.length === 0) {\n throw new Error('buildEvidenceVector: at least 1 objective required')\n }\n const confidence = opts.confidence ?? 0.95\n const resamples = opts.resamples ?? 2000\n const seed = opts.seed ?? 1337\n const baseline = ctx.baselineJudgeScores ?? ctx.judgeScores\n const scenarioIds = new Set(ctx.scenarios.map((s) => s.id))\n\n const axes: AxisEvidence[] = []\n for (const obj of objectives) {\n let select: (s: JudgeScore) => number | undefined\n if (obj.source.kind === 'composite') {\n select = (s) => s.composite\n } else {\n const dim = obj.source.dimension\n select = (s) => s.dimensions[dim]\n }\n const paired = pairHoldout(ctx.judgeScores, baseline, scenarioIds, select)\n // Orient to the good direction: maximize ⇒ bootstrap (candidate − baseline);\n // minimize ⇒ bootstrap (baseline − candidate) by swapping args, so a\n // positive bootstrap always reads as \"candidate better on this axis\".\n const before = obj.direction === 'maximize' ? paired.before : paired.after\n const after = obj.direction === 'maximize' ? paired.after : paired.before\n const n = paired.before.length\n const binaryScale = obj.binaryScale\n const floorTolerance =\n obj.floorTolerance ?? 0.05 * (binaryScale ?? detectScale([...paired.before, ...paired.after]))\n const gainThreshold = obj.gainThreshold ?? 0\n // Axes are decided on the MEAN paired delta — which for a pass/fail axis is\n // exactly the change in success rate. The median is structurally blind on\n // the shapes eval data lands in: with most pairs tied its bootstrap CI\n // collapses to [0,0] and the axis reads 'flat', hiding real gains AND real\n // regressions — pass/fail axes on ANY encoding ({0,1} and the 0-100 one\n // `detectScale` exists for), and low-cardinality axes even below half ties.\n // Orthogonal to `pairedDeltaTest`'s own small-sample switch: that picks the\n // TEST (bootstrap CI at n ≥ 20, exact sign test below), this picks the\n // ESTIMATOR the test is applied to. Both are needed — an exact sign test on\n // a tie-pinned median is still blind.\n const bootstrapStatistic = opts.statistic ?? DECISION_PAIRED_DELTA_STATISTIC\n // Both burdens of proof route through the ONE shared rule\n // (`decidePairedPromotion`), so a pass/fail axis is judged on Tango's score\n // interval — the only paired-binary construction valid at the nonzero\n // margins `gainThreshold` / `floorTolerance` create — and a zero-width\n // interval cannot buy a verdict in either direction.\n const improvement = decidePairedPromotion(before, after, {\n confidence,\n resamples,\n statistic: bootstrapStatistic,\n seed,\n threshold: gainThreshold,\n minPairs: opts.minProductiveRuns,\n binaryScale,\n })\n const regression = decidePairedPromotion(after, before, {\n confidence,\n resamples,\n statistic: bootstrapStatistic,\n seed,\n threshold: floorTolerance,\n minPairs: opts.minProductiveRuns,\n binaryScale,\n })\n const bootstrap =\n improvement.bootstrap ??\n pairedBootstrap(before, after, {\n confidence,\n resamples,\n statistic: bootstrapStatistic,\n seed,\n })\n // A floor breach fires on EITHER burden of proof, because they cover\n // different failures and the floor is the anti-Goodhart guard:\n // - `improvement.low < -floorTolerance` — the CREDIBLE WORST CASE exceeds\n // the tolerance. This is the contract `AxisEvidence.floorTolerance` and\n // `paretoPolicy`'s own reason string state, and it is the conservative\n // posture a safety axis needs: block unless the data can rule the\n // breach out, rather than waiting for the breach to be proven. Read off\n // the DECIDING interval, so a pass/fail axis is not screened by a\n // bootstrap that is pinned wherever ties dominate.\n // - `regression.promote` — a PROVEN drop past the tolerance. Adds the\n // small-sample path, where the decision is an exact sign test because\n // the bootstrap interval is descriptive only.\n // A tied binary axis still has uncertainty about unseen discordant pairs.\n // Its diagnostic bootstrap collapses to zero, so only the deciding score\n // interval can establish that a regression stays within the declared floor.\n const floorBreached = improvement.low < -floorTolerance || regression.promote\n // Floor check precedes the gain check: a credible regression must never be\n // masked as \"improved\". With the defaults (gainThreshold 0, positive floor)\n // the regions are disjoint and order is moot, but a consumer who sets a\n // negative gainThreshold (\"accept small dips\") could otherwise have a real\n // floor breach classified as a gain — anti-Goodhart wins the tie.\n const verdict: AxisVerdict = !improvement.sufficient\n ? 'few_runs'\n : improvement.indeterminate\n ? 'indeterminate'\n : floorBreached\n ? 'regressed'\n : improvement.promote\n ? 'improved'\n : 'flat'\n axes.push({\n name: obj.name,\n source: obj.source,\n direction: obj.direction,\n bootstrap,\n bootstrapStatistic,\n ci: { low: improvement.low, high: improvement.high },\n decisionStatistic: improvement.statistic,\n mcnemar: improvement.mcnemar,\n indeterminate: improvement.indeterminate,\n n,\n minimumRequired: improvement.minimumPairs,\n decisionMethod: improvement.method,\n gainThreshold,\n floorTolerance,\n verdict,\n })\n }\n const ns = axes.map((a) => a.n).filter((n) => n > 0)\n const minN = ns.length > 0 ? Math.min(...ns) : 0\n return { axes, minN, cost: { candidate: ctx.cost.candidate, baseline: ctx.cost.baseline } }\n}\n\n/**\n * Require a supported gain and every configured regression floor to clear.\n * A failed floor holds the candidate, including when uncertainty permits a loss.\n * Missing or indeterminate evidence requires more work; no gain holds release.\n */\nexport const paretoPolicy: PromotionPolicy = (ev) => {\n const contributingGates = ev.axes.map((ax) => ({\n name: `objective:${ax.name}`,\n status:\n ax.verdict === 'regressed'\n ? ('fail' as const)\n : ax.verdict === 'few_runs' || ax.verdict === 'indeterminate'\n ? ('not_evaluated' as const)\n : ('pass' as const),\n detail: {\n direction: ax.direction,\n source: ax.source,\n verdict: ax.verdict,\n n: ax.n,\n deltaMedian: ax.bootstrap.median,\n ciLow: ax.ci.low,\n ciHigh: ax.ci.high,\n decisionStatistic: ax.decisionStatistic,\n decisionMethod: ax.decisionMethod,\n mcnemar: ax.mcnemar,\n indeterminate: ax.indeterminate,\n bootstrapCiLow: ax.bootstrap.low,\n bootstrapCiHigh: ax.bootstrap.high,\n confidence: ax.bootstrap.confidence,\n gainThreshold: ax.gainThreshold,\n floorTolerance: ax.floorTolerance,\n },\n }))\n\n const regressed = ev.axes.filter((a) => a.verdict === 'regressed')\n const insufficient = ev.axes.filter(\n (a) => a.verdict === 'few_runs' || a.verdict === 'indeterminate',\n )\n const improved = ev.axes.filter((a) => a.verdict === 'improved')\n\n let decision: GateDecision\n const reasons: string[] = []\n if (regressed.length > 0) {\n // A gain on another axis cannot excuse an unresolved regression floor.\n decision = 'hold'\n for (const a of regressed) {\n reasons.push(\n `objective '${a.name}' did not clear its regression floor -${a.floorTolerance}: good-direction CI [${a.ci.low.toFixed(3)}, ${a.ci.high.toFixed(3)}] (n=${a.n})`,\n )\n }\n } else if (insufficient.length > 0) {\n // An unresolved axis cannot establish either a gain or a safe floor.\n decision = 'need_more_work'\n for (const a of insufficient) {\n reasons.push(\n a.verdict === 'few_runs'\n ? `objective '${a.name}' has only n=${a.n} paired runs — insufficient evidence to claim significance`\n : `objective '${a.name}' has an indeterminate deciding CI [${a.ci.low}, ${a.ci.high}] — insufficient evidence to clear its regression floor (n=${a.n})`,\n )\n }\n } else if (improved.length > 0) {\n decision = 'ship'\n reasons.push(\n `Supported objective gain: ${improved\n .map(\n (a) =>\n `'${a.name}' +${a.ci.low > 0 ? a.ci.low.toFixed(3) : a.bootstrap.mean.toFixed(3)} (CI.low ${a.ci.low.toFixed(3)})`,\n )\n .join(', ')}; every regression floor cleared`,\n )\n } else {\n // Every floor cleared, but no objective demonstrated a significant gain.\n decision = 'hold'\n reasons.push(\n 'no Pareto improvement: no objective shows a significant gain; every regression floor cleared',\n )\n }\n\n // `delta` surfaces the composite axis if present, else the first axis — a\n // single convenience scalar; the vector lives in `contributingGates`.\n const composite = ev.axes.find((a) => a.source.kind === 'composite') ?? ev.axes[0]\n return { decision, reasons, contributingGates, delta: composite?.bootstrap.median }\n}\n\nexport interface ParetoSignificanceGateOptions extends BuildEvidenceVectorOptions {\n /** The objective vector. Every axis is both a gain source and a safety floor. */\n objectives: PromotionObjective[]\n /** Strategy applied to the evidence vector. Default `paretoPolicy`. Override\n * to run a stricter/looser strategy over the SAME bus (competing policies). */\n policy?: PromotionPolicy\n /** Override the gate name in reports. */\n name?: string\n}\n\n/**\n * Wrap the bus + a policy as a `Gate`. Plugs into the existing\n * `runImprovementLoop({ gate })` slot and composes via `composeGate`; default\n * loop behavior is unchanged because consumers opt in by passing this gate.\n */\nexport function paretoSignificanceGate<TArtifact = unknown, TScenario extends Scenario = Scenario>(\n options: ParetoSignificanceGateOptions,\n): Gate<TArtifact, TScenario> {\n if (options.objectives.length === 0) {\n throw new Error('paretoSignificanceGate: at least 1 objective required')\n }\n const policy = options.policy ?? paretoPolicy\n return {\n name: options.name ?? 'paretoSignificanceGate',\n async decide(ctx: GateContext<TArtifact, TScenario>): Promise<GateResult> {\n const ev = buildEvidenceVector(ctx, options.objectives, options)\n return policy(ev)\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"promotion-policy-CWW35UWq.js","names":[],"sources":["../src/campaign/gates/promotion-policy.ts"],"sourcesContent":["/**\n * Build paired evidence for each objective and apply a promotion policy.\n * The default policy requires at least one gain and every regression floor to\n * clear. A floor can fail because a larger loss remains plausible; that does\n * not demonstrate an observed regression. Missing evidence stays unresolved.\n * Confidence intervals apply to each axis, without a multiplicity adjustment.\n *\n * Cost and latency remain aggregate constraints because GateContext does not\n * supply paired observations for them. Compose a budget gate when needed.\n */\n\nimport {\n decidePairedPromotion,\n type PairedDecisionMethod,\n type PairedDecisionStatistic,\n type PairedMcNemarEvidence,\n} from '../../paired-promotion-decision'\nimport type { Direction } from '../../pareto'\nimport {\n DECISION_PAIRED_DELTA_STATISTIC,\n type PairedBootstrapResult,\n pairedBootstrap,\n} from '../../statistics'\nimport type { Gate, GateContext, GateDecision, GateResult, JudgeScore, Scenario } from '../types'\nimport { detectScale, pairHoldout } from './statistical-heldout'\n\n/** Where an objective's per-cell scalar comes from. `composite` reads the\n * judge's composite; `dimension` reads a named per-dimension score. */\nexport type ObjectiveSource = { kind: 'composite' } | { kind: 'dimension'; dimension: string }\n\nexport interface PromotionObjective {\n /** Stable label used in reports + `contributingGates`. */\n name: string\n source: ObjectiveSource\n /** 'maximize' (quality dims) or 'minimize' (error/risk/length dims). Orients\n * the paired delta so a positive bootstrap always means \"candidate better\". */\n direction: Direction\n /** Declared binary support {0, binaryScale}, including zero-only observations.\n * Must be finite and positive; paired cell scores must be 0 or this scale.\n * Uses the risk-difference mean and rejects the 'median' statistic. */\n binaryScale?: number\n /** The good-direction paired-delta CI lower bound must EXCEED this to count\n * as a significant gain on this axis. Interpreted in the judge's native\n * scale. Default 0 (⇒ \"confidently better\"). */\n gainThreshold?: number\n /** A floor breach (regression) is declared when the good-direction CI lower\n * bound is below −floorTolerance, or when the exact small-sample test proves\n * a drop past it. Defaults to 0.05 times the declared binary scale, or\n * auto-scales off observed magnitudes (0.05 on [0,1], 5 on 0-100). */\n floorTolerance?: number\n}\n\n/** Per-axis verdict from the shared paired decision rule.\n * 'regressed' includes uncertainty that prevents clearing the regression floor. */\nexport type AxisVerdict = 'improved' | 'regressed' | 'flat' | 'few_runs' | 'indeterminate'\n\nexport interface AxisEvidence {\n name: string\n source: ObjectiveSource\n direction: Direction\n /** Paired bootstrap on the GOOD-DIRECTION delta (oriented by `direction`):\n * a positive value means the candidate is better on this axis.\n *\n * DIAGNOSTIC on a pass/fail axis: there the verdict is decided on Tango's\n * score interval instead, because a percentile bootstrap over a three-atom\n * delta lattice is not a valid interval at the nonzero margin `floorTolerance`\n * and `gainThreshold` create. `ci` carries the interval that decided. */\n bootstrap: PairedBootstrapResult\n /** Which paired statistic `bootstrap.low`/`.high` bracket. `'mean'` unless the\n * caller asked for the median — on a pass/fail axis the median and its whole\n * CI are pinned at 0 by tie domination and can see neither a gain nor a\n * regression. `bootstrap.median` still carries the median point estimate. */\n bootstrapStatistic: 'median' | 'mean'\n /** The interval the axis verdict was actually decided on, good-direction and\n * in the axis's native units. */\n ci: { low: number; high: number }\n /** Which estimator produced `ci`. */\n decisionStatistic: PairedDecisionStatistic\n /** McNemar's exact evidence on a pass/fail axis; null otherwise. */\n mcnemar: PairedMcNemarEvidence | null\n /** `ci` has zero width or non-finite bounds. It cannot establish a gain or\n * clear a regression floor, regardless of the point estimate. */\n indeterminate: boolean\n /** Paired observations contributing to this axis. */\n n: number\n minimumRequired: number\n decisionMethod: PairedDecisionMethod\n gainThreshold: number\n floorTolerance: number\n verdict: AxisVerdict\n}\n\nexport interface EvidenceVector {\n /** One entry per objective — NOTHING averaged across axes. */\n axes: AxisEvidence[]\n /** Smallest paired n across axes that produced observations — the binding\n * evidence-sufficiency constraint. 0 when no axis produced observations. */\n minN: number\n /** Aggregate per-side cost from the gate context (a constraint input, not a\n * CI axis — see the module header). */\n cost: { candidate: number; baseline: number }\n}\n\n/** A promotion strategy: a pure function from the evidence vector to a verdict.\n * Many policies can run over the same `EvidenceVector` and disagree — that's\n * the point (competing strategies, shared evidence). */\nexport type PromotionPolicy = (ev: EvidenceVector) => GateResult\n\nexport interface BuildEvidenceVectorOptions {\n /** Minimum paired observations before an axis can claim significance; below\n * it the axis is `few_runs`. The exact small-sample test may require more\n * observations at the selected confidence. */\n minProductiveRuns?: number\n /** Confidence level for every axis bootstrap. Default 0.95. */\n confidence?: number\n /** Bootstrap resamples. Default 2000. */\n resamples?: number\n /** Fixed bootstrap seed for a deterministic, reproducible verdict. Default 1337. */\n seed?: number\n /** Paired statistic every axis CI is computed on. Default `'mean'` — see\n * {@link DECISION_PAIRED_DELTA_STATISTIC} for why the median is not. */\n statistic?: 'mean' | 'median'\n}\n\n/**\n * The Evidence Bus. For each objective, pair candidate vs baseline by full\n * cellId and bootstrap a CI on the good-direction paired delta. Reuses the\n * exact `pairHoldout` + `pairedBootstrap` machinery the held-out gate uses, so\n * a single source of truth governs pairing granularity + scale handling.\n */\nexport function buildEvidenceVector<TArtifact, TScenario extends Scenario>(\n ctx: GateContext<TArtifact, TScenario>,\n objectives: PromotionObjective[],\n opts: BuildEvidenceVectorOptions = {},\n): EvidenceVector {\n if (objectives.length === 0) {\n throw new Error('buildEvidenceVector: at least 1 objective required')\n }\n const confidence = opts.confidence ?? 0.95\n const resamples = opts.resamples ?? 2000\n const seed = opts.seed ?? 1337\n const baseline = ctx.baselineJudgeScores ?? ctx.judgeScores\n const scenarioIds = new Set(ctx.scenarios.map((s) => s.id))\n\n const axes: AxisEvidence[] = []\n for (const obj of objectives) {\n let select: (s: JudgeScore) => number | undefined\n if (obj.source.kind === 'composite') {\n select = (s) => s.composite\n } else {\n const dim = obj.source.dimension\n select = (s) => s.dimensions[dim]\n }\n const paired = pairHoldout(ctx.judgeScores, baseline, scenarioIds, select)\n // Orient to the good direction: maximize ⇒ bootstrap (candidate − baseline);\n // minimize ⇒ bootstrap (baseline − candidate) by swapping args, so a\n // positive bootstrap always reads as \"candidate better on this axis\".\n const before = obj.direction === 'maximize' ? paired.before : paired.after\n const after = obj.direction === 'maximize' ? paired.after : paired.before\n const n = paired.before.length\n const binaryScale = obj.binaryScale\n const floorTolerance =\n obj.floorTolerance ?? 0.05 * (binaryScale ?? detectScale([...paired.before, ...paired.after]))\n const gainThreshold = obj.gainThreshold ?? 0\n // Axes are decided on the MEAN paired delta — which for a pass/fail axis is\n // exactly the change in success rate. The median is structurally blind on\n // the shapes eval data lands in: with most pairs tied its bootstrap CI\n // collapses to [0,0] and the axis reads 'flat', hiding real gains AND real\n // regressions — pass/fail axes on ANY encoding ({0,1} and the 0-100 one\n // `detectScale` exists for), and low-cardinality axes even below half ties.\n // Orthogonal to `pairedDeltaTest`'s own small-sample switch: that picks the\n // TEST (bootstrap CI at n ≥ 20, exact sign test below), this picks the\n // ESTIMATOR the test is applied to. Both are needed — an exact sign test on\n // a tie-pinned median is still blind.\n const bootstrapStatistic = opts.statistic ?? DECISION_PAIRED_DELTA_STATISTIC\n // Both burdens of proof route through the ONE shared rule\n // (`decidePairedPromotion`), so a pass/fail axis is judged on Tango's score\n // interval — the only paired-binary construction valid at the nonzero\n // margins `gainThreshold` / `floorTolerance` create — and a zero-width\n // interval cannot buy a verdict in either direction.\n const improvement = decidePairedPromotion(before, after, {\n confidence,\n resamples,\n statistic: bootstrapStatistic,\n seed,\n threshold: gainThreshold,\n minPairs: opts.minProductiveRuns,\n binaryScale,\n })\n const regression = decidePairedPromotion(after, before, {\n confidence,\n resamples,\n statistic: bootstrapStatistic,\n seed,\n threshold: floorTolerance,\n minPairs: opts.minProductiveRuns,\n binaryScale,\n })\n const bootstrap =\n improvement.bootstrap ??\n pairedBootstrap(before, after, {\n confidence,\n resamples,\n statistic: bootstrapStatistic,\n seed,\n })\n // A floor breach fires on EITHER burden of proof, because they cover\n // different failures and the floor is the anti-Goodhart guard:\n // - `improvement.low < -floorTolerance` — the CREDIBLE WORST CASE exceeds\n // the tolerance. This is the contract `AxisEvidence.floorTolerance` and\n // `paretoPolicy`'s own reason string state, and it is the conservative\n // posture a safety axis needs: block unless the data can rule the\n // breach out, rather than waiting for the breach to be proven. Read off\n // the DECIDING interval, so a pass/fail axis is not screened by a\n // bootstrap that is pinned wherever ties dominate.\n // - `regression.promote` — a PROVEN drop past the tolerance. Adds the\n // small-sample path, where the decision is an exact sign test because\n // the bootstrap interval is descriptive only.\n // A tied binary axis still has uncertainty about unseen discordant pairs.\n // Its diagnostic bootstrap collapses to zero, so only the deciding score\n // interval can establish that a regression stays within the declared floor.\n const floorBreached = improvement.low < -floorTolerance || regression.promote\n // Floor check precedes the gain check: a credible regression must never be\n // masked as \"improved\". With the defaults (gainThreshold 0, positive floor)\n // the regions are disjoint and order is moot, but a consumer who sets a\n // negative gainThreshold (\"accept small dips\") could otherwise have a real\n // floor breach classified as a gain — anti-Goodhart wins the tie.\n const verdict: AxisVerdict = !improvement.sufficient\n ? 'few_runs'\n : improvement.indeterminate\n ? 'indeterminate'\n : floorBreached\n ? 'regressed'\n : improvement.promote\n ? 'improved'\n : 'flat'\n axes.push({\n name: obj.name,\n source: obj.source,\n direction: obj.direction,\n bootstrap,\n bootstrapStatistic,\n ci: { low: improvement.low, high: improvement.high },\n decisionStatistic: improvement.statistic,\n mcnemar: improvement.mcnemar,\n indeterminate: improvement.indeterminate,\n n,\n minimumRequired: improvement.minimumPairs,\n decisionMethod: improvement.method,\n gainThreshold,\n floorTolerance,\n verdict,\n })\n }\n const ns = axes.map((a) => a.n).filter((n) => n > 0)\n const minN = ns.length > 0 ? Math.min(...ns) : 0\n return { axes, minN, cost: { candidate: ctx.cost.candidate, baseline: ctx.cost.baseline } }\n}\n\n/**\n * Require a supported gain and every configured regression floor to clear.\n * A failed floor holds the candidate, including when uncertainty permits a loss.\n * Missing or indeterminate evidence requires more work; no gain holds release.\n */\nexport const paretoPolicy: PromotionPolicy = (ev) => {\n const contributingGates = ev.axes.map((ax) => ({\n name: `objective:${ax.name}`,\n status:\n ax.verdict === 'regressed'\n ? ('fail' as const)\n : ax.verdict === 'few_runs' || ax.verdict === 'indeterminate'\n ? ('not_evaluated' as const)\n : ('pass' as const),\n detail: {\n direction: ax.direction,\n source: ax.source,\n verdict: ax.verdict,\n n: ax.n,\n deltaMedian: ax.bootstrap.median,\n ciLow: ax.ci.low,\n ciHigh: ax.ci.high,\n decisionStatistic: ax.decisionStatistic,\n decisionMethod: ax.decisionMethod,\n mcnemar: ax.mcnemar,\n indeterminate: ax.indeterminate,\n bootstrapCiLow: ax.bootstrap.low,\n bootstrapCiHigh: ax.bootstrap.high,\n confidence: ax.bootstrap.confidence,\n gainThreshold: ax.gainThreshold,\n floorTolerance: ax.floorTolerance,\n },\n }))\n\n const regressed = ev.axes.filter((a) => a.verdict === 'regressed')\n const insufficient = ev.axes.filter(\n (a) => a.verdict === 'few_runs' || a.verdict === 'indeterminate',\n )\n const improved = ev.axes.filter((a) => a.verdict === 'improved')\n\n let decision: GateDecision\n const reasons: string[] = []\n if (regressed.length > 0) {\n // A gain on another axis cannot excuse an unresolved regression floor.\n decision = 'hold'\n for (const a of regressed) {\n reasons.push(\n `objective '${a.name}' did not clear its regression floor -${a.floorTolerance}: good-direction CI [${a.ci.low.toFixed(3)}, ${a.ci.high.toFixed(3)}] (n=${a.n})`,\n )\n }\n } else if (insufficient.length > 0) {\n // An unresolved axis cannot establish either a gain or a safe floor.\n decision = 'need_more_work'\n for (const a of insufficient) {\n reasons.push(\n a.verdict === 'few_runs'\n ? `objective '${a.name}' has only n=${a.n} paired runs — insufficient evidence to claim significance`\n : `objective '${a.name}' has an indeterminate deciding CI [${a.ci.low}, ${a.ci.high}] — insufficient evidence to clear its regression floor (n=${a.n})`,\n )\n }\n } else if (improved.length > 0) {\n decision = 'ship'\n reasons.push(\n `Supported objective gain: ${improved\n .map(\n (a) =>\n `'${a.name}' +${a.ci.low > 0 ? a.ci.low.toFixed(3) : a.bootstrap.mean.toFixed(3)} (CI.low ${a.ci.low.toFixed(3)})`,\n )\n .join(', ')}; every regression floor cleared`,\n )\n } else {\n // Every floor cleared, but no objective demonstrated a significant gain.\n decision = 'hold'\n reasons.push(\n 'no Pareto improvement: no objective shows a significant gain; every regression floor cleared',\n )\n }\n\n // `delta` surfaces the composite axis if present, else the first axis — a\n // single convenience scalar; the vector lives in `contributingGates`.\n const composite = ev.axes.find((a) => a.source.kind === 'composite') ?? ev.axes[0]\n return { decision, reasons, contributingGates, delta: composite?.bootstrap.median }\n}\n\nexport interface ParetoSignificanceGateOptions extends BuildEvidenceVectorOptions {\n /** The objective vector. Every axis is both a gain source and a safety floor. */\n objectives: PromotionObjective[]\n /** Strategy applied to the evidence vector. Default `paretoPolicy`. Override\n * to run a stricter/looser strategy over the SAME bus (competing policies). */\n policy?: PromotionPolicy\n /** Override the gate name in reports. */\n name?: string\n}\n\n/**\n * Wrap the bus + a policy as a `Gate`. Plugs into the existing\n * `runImprovementLoop({ gate })` slot and composes via `composeGate`; default\n * loop behavior is unchanged because consumers opt in by passing this gate.\n */\nexport function paretoSignificanceGate<TArtifact = unknown, TScenario extends Scenario = Scenario>(\n options: ParetoSignificanceGateOptions,\n): Gate<TArtifact, TScenario> {\n if (options.objectives.length === 0) {\n throw new Error('paretoSignificanceGate: at least 1 objective required')\n }\n const policy = options.policy ?? paretoPolicy\n return {\n name: options.name ?? 'paretoSignificanceGate',\n async decide(ctx: GateContext<TArtifact, TScenario>): Promise<GateResult> {\n const ev = buildEvidenceVector(ctx, options.objectives, options)\n return policy(ev)\n },\n }\n}\n"],"mappings":"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"}
@@ -129,6 +129,6 @@ function runMetricExtractor(metric) {
129
129
  };
130
130
  }
131
131
  //#endregion
132
- export { hasCapturedToolArgs as a, llmSpans as c, runsForScenario as d, toolSpans as f, groupBy as i, runFailureClass as l, aggregateLlm as n, isRunMetric as o, argHash as r, judgeSpans as s, RUN_METRICS as t, runMetricExtractor as u };
132
+ export { hasCapturedToolArgs as a, runFailureClass as c, toolSpans as d, groupBy as i, runMetricExtractor as l, aggregateLlm as n, judgeSpans as o, argHash as r, llmSpans as s, RUN_METRICS as t, runsForScenario as u };
133
133
 
134
- //# sourceMappingURL=query-DlV0pdx4.js.map
134
+ //# sourceMappingURL=query-B1D1qp2Z.js.map