@tangle-network/agent-eval 0.200.1 → 0.202.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 (241) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/adapters/http.d.ts +8 -9
  3. package/dist/adapters/http.d.ts.map +1 -1
  4. package/dist/adapters/http.js +2 -2
  5. package/dist/adapters/http.js.map +1 -1
  6. package/dist/agent-profile-DDfIjdyS.d.ts.map +1 -1
  7. package/dist/analyst/index.d.ts +76 -76
  8. package/dist/analyst/index.d.ts.map +1 -1
  9. package/dist/analyst/index.js +6 -6
  10. package/dist/{analyze-runs-DFbp2gB8.js → analyze-runs-BrIibNd0.js} +47 -42
  11. package/dist/{analyze-runs-DFbp2gB8.js.map → analyze-runs-BrIibNd0.js.map} +1 -1
  12. package/dist/{ast-CP9ae9B0.js → ast-DJ6Bc7ab.js} +5 -4
  13. package/dist/{ast-CP9ae9B0.js.map → ast-DJ6Bc7ab.js.map} +1 -1
  14. package/dist/authenticity/index.d.ts +14 -15
  15. package/dist/authenticity/index.d.ts.map +1 -1
  16. package/dist/{baseline-BC-eBZ7U.js → baseline-CVKsfWTg.js} +2 -2
  17. package/dist/{baseline-BC-eBZ7U.js.map → baseline-CVKsfWTg.js.map} +1 -1
  18. package/dist/{benchmark-BeQJyrRC.js → benchmark-BywPeAUo.js} +4 -3
  19. package/dist/{benchmark-BeQJyrRC.js.map → benchmark-BywPeAUo.js.map} +1 -1
  20. package/dist/{benchmark-command-EBmuMUBo.js → benchmark-command-DFs_i62N.js} +24 -23
  21. package/dist/{benchmark-command-EBmuMUBo.js.map → benchmark-command-DFs_i62N.js.map} +1 -1
  22. package/dist/benchmarks/index.d.ts +13 -13
  23. package/dist/benchmarks/index.d.ts.map +1 -1
  24. package/dist/benchmarks/index.js +3 -3
  25. package/dist/{bounded-process-BBZob7vl.js → bounded-process-Cj9YjXbE.js} +3 -3
  26. package/dist/{bounded-process-BBZob7vl.js.map → bounded-process-Cj9YjXbE.js.map} +1 -1
  27. package/dist/builder-eval/index.d.ts +16 -17
  28. package/dist/builder-eval/index.d.ts.map +1 -1
  29. package/dist/builder-eval/index.js +12 -10
  30. package/dist/builder-eval/index.js.map +1 -1
  31. package/dist/campaign/index.d.ts +2 -2
  32. package/dist/campaign/index.js +12 -12
  33. package/dist/{campaign-L391bmir.js → campaign-XXmOU0R0.js} +265 -27
  34. package/dist/campaign-XXmOU0R0.js.map +1 -0
  35. package/dist/{campaign-evidence-jL_U7Ikv.js → campaign-evidence-Dt_rWPk2.js} +4 -4
  36. package/dist/{campaign-evidence-jL_U7Ikv.js.map → campaign-evidence-Dt_rWPk2.js.map} +1 -1
  37. package/dist/{chat-client-CJORSG_J.js → chat-client-DUhWNQQS.js} +5 -4
  38. package/dist/{chat-client-CJORSG_J.js.map → chat-client-DUhWNQQS.js.map} +1 -1
  39. package/dist/{chat-json-call-DnlDjpco.js → chat-json-call-Dx_sHzqm.js} +2 -2
  40. package/dist/{chat-json-call-DnlDjpco.js.map → chat-json-call-Dx_sHzqm.js.map} +1 -1
  41. package/dist/{claim-V9Zqbq0J.js → claim-BXcZw-jo.js} +2 -2
  42. package/dist/{claim-V9Zqbq0J.js.map → claim-BXcZw-jo.js.map} +1 -1
  43. package/dist/cli.js +12 -11
  44. package/dist/cli.js.map +1 -1
  45. package/dist/contract/index.d.ts +3 -3
  46. package/dist/contract/index.js +22 -19
  47. package/dist/contract/index.js.map +1 -1
  48. package/dist/{default-registry-xMLX160E.js → default-registry-D4qXx2aG.js} +2 -2
  49. package/dist/{default-registry-xMLX160E.js.map → default-registry-D4qXx2aG.js.map} +1 -1
  50. package/dist/{define-agent-eval-7q_UwsTt.js → define-agent-eval-CJN9dTQw.js} +10 -9
  51. package/dist/{define-agent-eval-7q_UwsTt.js.map → define-agent-eval-CJN9dTQw.js.map} +1 -1
  52. package/dist/{define-agent-eval-1YuvJpOs.d.ts → define-agent-eval-DQXLPqWc.d.ts} +2 -2
  53. package/dist/define-agent-eval-DQXLPqWc.d.ts.map +1 -0
  54. package/dist/{descriptive-1V17A-qa.js → descriptive-D9LUFk1-.js} +2 -2
  55. package/dist/{descriptive-1V17A-qa.js.map → descriptive-D9LUFk1-.js.map} +1 -1
  56. package/dist/diagnosis/index.d.ts +7 -7
  57. package/dist/diagnosis/index.d.ts.map +1 -1
  58. package/dist/diagnosis/index.js +2 -2
  59. package/dist/diagnosis/index.js.map +1 -1
  60. package/dist/{dspy-rlm-engine-B5T43BkZ.js → dspy-rlm-engine-US8YsYGq.js} +5 -5
  61. package/dist/{dspy-rlm-engine-B5T43BkZ.js.map → dspy-rlm-engine-US8YsYGq.js.map} +1 -1
  62. package/dist/{eval-campaign-GapmPHIA.js → eval-campaign-ha_aWtk7.js} +14 -12
  63. package/dist/{eval-campaign-GapmPHIA.js.map → eval-campaign-ha_aWtk7.js.map} +1 -1
  64. package/dist/evaluation.d.ts +9 -10
  65. package/dist/evaluation.d.ts.map +1 -1
  66. package/dist/experiment/index.d.ts +29 -29
  67. package/dist/experiment/index.d.ts.map +1 -1
  68. package/dist/experiment/index.js +13 -12
  69. package/dist/experiment/index.js.map +1 -1
  70. package/dist/{exporters-DEcXbvgO.js → exporters-Coiv-qO2.js} +2 -2
  71. package/dist/{exporters-DEcXbvgO.js.map → exporters-Coiv-qO2.js.map} +1 -1
  72. package/dist/{external-optimizer-process-CEXcCKqd.js → external-optimizer-process-KLcw0Vnc.js} +2 -2
  73. package/dist/{external-optimizer-process-CEXcCKqd.js.map → external-optimizer-process-KLcw0Vnc.js.map} +1 -1
  74. package/dist/{external-optimizer-subprocess-laM1-UDV.js → external-optimizer-subprocess-CkyUUV7h.js} +4 -4
  75. package/dist/{external-optimizer-subprocess-laM1-UDV.js.map → external-optimizer-subprocess-CkyUUV7h.js.map} +1 -1
  76. package/dist/fuzz.d.ts +15 -15
  77. package/dist/fuzz.d.ts.map +1 -1
  78. package/dist/{hf-dataset-CRqtSjnR.js → hf-dataset-kjIfIK_e.js} +3 -3
  79. package/dist/{hf-dataset-CRqtSjnR.js.map → hf-dataset-kjIfIK_e.js.map} +1 -1
  80. package/dist/hosted/index.d.ts +8 -8
  81. package/dist/hosted/index.d.ts.map +1 -1
  82. package/dist/hosted/index.js +1 -1
  83. package/dist/{index-EkRNsLr4.d.ts → index-D1ZX28rV.d.ts} +3 -3
  84. package/dist/{index-EkRNsLr4.d.ts.map → index-D1ZX28rV.d.ts.map} +1 -1
  85. package/dist/{index-D4pC7Tvd2.d.ts → index-D4pC7Tvd.d.ts} +1 -1
  86. package/dist/index-D4pC7Tvd.d.ts.map +1 -0
  87. package/dist/{index-DNgf5gyG2.d.ts → index-DNgf5gyG.d.ts} +1 -1
  88. package/dist/index-DNgf5gyG.d.ts.map +1 -0
  89. package/dist/{index-DKmrHA02.d.ts → index-giYkbitB.d.ts} +47 -2
  90. package/dist/index-giYkbitB.d.ts.map +1 -0
  91. package/dist/index.d.ts +126 -126
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +52 -42
  94. package/dist/index.js.map +1 -1
  95. package/dist/{integrity-BFP4kjGj.js → integrity-BvnVARVl.js} +30 -29
  96. package/dist/{integrity-BFP4kjGj.js.map → integrity-BvnVARVl.js.map} +1 -1
  97. package/dist/{integrity-BvHANvnp.js → integrity-uwK7XC-8.js} +2 -2
  98. package/dist/{integrity-BvHANvnp.js.map → integrity-uwK7XC-8.js.map} +1 -1
  99. package/dist/{internal-BMFSR8Ns.js → internal-BOe3zmjQ.js} +2 -2
  100. package/dist/{internal-BMFSR8Ns.js.map → internal-BOe3zmjQ.js.map} +1 -1
  101. package/dist/{jev-review-Cu9xtIu4.js → jev-review-DZEjDzlF.js} +7 -5
  102. package/dist/{jev-review-Cu9xtIu4.js.map → jev-review-DZEjDzlF.js.map} +1 -1
  103. package/dist/jev.d.ts +10 -10
  104. package/dist/jev.d.ts.map +1 -1
  105. package/dist/jev.js +2 -2
  106. package/dist/{judge-calibration-DYmaBtJr.js → judge-calibration-C5zgtiJT.js} +2 -2
  107. package/dist/{judge-calibration-DYmaBtJr.js.map → judge-calibration-C5zgtiJT.js.map} +1 -1
  108. package/dist/kind-factory-CdPPHtGZ.js.map +1 -1
  109. package/dist/{llm-client-c_bKHTXz.js → llm-client-Bhqoglar.js} +6 -4
  110. package/dist/{llm-client-c_bKHTXz.js.map → llm-client-Bhqoglar.js.map} +1 -1
  111. package/dist/{llm-judge-CL6f3utD.js → llm-judge-C-0P1WiT.js} +26 -21
  112. package/dist/{llm-judge-CL6f3utD.js.map → llm-judge-C-0P1WiT.js.map} +1 -1
  113. package/dist/matrix/index.d.ts +1 -1
  114. package/dist/matrix/index.js +1 -1
  115. package/dist/{matrix-_NLSWsKM.d.ts → matrix--IZKROxD.d.ts} +2 -2
  116. package/dist/{matrix-_NLSWsKM.d.ts.map → matrix--IZKROxD.d.ts.map} +1 -1
  117. package/dist/{matrix-pWinRMPl.js → matrix-DnlHn5x1.js} +8 -6
  118. package/dist/{matrix-pWinRMPl.js.map → matrix-DnlHn5x1.js.map} +1 -1
  119. package/dist/meta-eval/index.d.ts +102 -59
  120. package/dist/meta-eval/index.d.ts.map +1 -1
  121. package/dist/meta-eval/index.js +89 -25
  122. package/dist/meta-eval/index.js.map +1 -1
  123. package/dist/{mint-iCD-W43s.js → mint-vlRckJEX.js} +2 -2
  124. package/dist/{mint-iCD-W43s.js.map → mint-vlRckJEX.js.map} +1 -1
  125. package/dist/multishot/golden/index.d.ts +23 -23
  126. package/dist/multishot/golden/index.d.ts.map +1 -1
  127. package/dist/multishot/golden/index.js +1 -1
  128. package/dist/multishot/index.d.ts +9 -9
  129. package/dist/multishot/index.d.ts.map +1 -1
  130. package/dist/multishot/index.js +1 -1
  131. package/dist/openapi.json +1 -1
  132. package/dist/{otel-spans-CQmSxtcS.js → otel-spans-C8YIJWy1.js} +9 -4
  133. package/dist/{otel-spans-CQmSxtcS.js.map → otel-spans-C8YIJWy1.js.map} +1 -1
  134. package/dist/{outcome-store-BXlkwMPR.js → outcome-store-DUcMj-3D.js} +2 -2
  135. package/dist/{outcome-store-BXlkwMPR.js.map → outcome-store-DUcMj-3D.js.map} +1 -1
  136. package/dist/package-version-D7lQHt_-.js.map +1 -1
  137. package/dist/{paired-arms-DKZFVPt8.js → paired-arms-DpS_h8ON.js} +8 -5
  138. package/dist/{paired-arms-DKZFVPt8.js.map → paired-arms-DpS_h8ON.js.map} +1 -1
  139. package/dist/{paired-binary-fzkwENI4.js → paired-binary-OMBVRtc-.js} +2 -2
  140. package/dist/{paired-binary-fzkwENI4.js.map → paired-binary-OMBVRtc-.js.map} +1 -1
  141. package/dist/{paired-promotion-decision-D5B1Cxc2.js → paired-promotion-decision-DhrmjdV8.js} +5 -4
  142. package/dist/{paired-promotion-decision-D5B1Cxc2.js.map → paired-promotion-decision-DhrmjdV8.js.map} +1 -1
  143. package/dist/{paired-tests-C8iCsioC.js → paired-tests-Ccr16gcW.js} +3 -3
  144. package/dist/{paired-tests-C8iCsioC.js.map → paired-tests-Ccr16gcW.js.map} +1 -1
  145. package/dist/pipelines/index.d.ts +19 -19
  146. package/dist/pipelines/index.d.ts.map +1 -1
  147. package/dist/pipelines/index.js +5 -4
  148. package/dist/pipelines/index.js.map +1 -1
  149. package/dist/{power-and-mde-K_CmO3dc.js → power-and-mde-bie6i965.js} +9 -6
  150. package/dist/{power-and-mde-K_CmO3dc.js.map → power-and-mde-bie6i965.js.map} +1 -1
  151. package/dist/{produced-state-DZMW8cuF.js → produced-state-CR2tAPLf.js} +8 -5
  152. package/dist/{produced-state-DZMW8cuF.js.map → produced-state-CR2tAPLf.js.map} +1 -1
  153. package/dist/{promotion-policy-C6FAJvmZ.js → promotion-policy-BI1RxzwD.js} +4 -4
  154. package/dist/{promotion-policy-C6FAJvmZ.js.map → promotion-policy-BI1RxzwD.js.map} +1 -1
  155. package/dist/{raw-provider-sink-CXuOhLvT.js → raw-provider-sink-rB2DYxr6.js} +2 -2
  156. package/dist/{raw-provider-sink-CXuOhLvT.js.map → raw-provider-sink-rB2DYxr6.js.map} +1 -1
  157. package/dist/{release-confidence-XQm_xjhR.js → release-confidence-CiFg1EPq.js} +3 -2
  158. package/dist/{release-confidence-XQm_xjhR.js.map → release-confidence-CiFg1EPq.js.map} +1 -1
  159. package/dist/{report-command-DKJ_yAIj.js → report-command-DzPpFgjB.js} +20 -18
  160. package/dist/{report-command-DKJ_yAIj.js.map → report-command-DzPpFgjB.js.map} +1 -1
  161. package/dist/reporting.js +5 -5
  162. package/dist/{reward-hacking-D0XwhVWE.js → reward-hacking-C-a__ShZ.js} +5 -3
  163. package/dist/{reward-hacking-D0XwhVWE.js.map → reward-hacking-C-a__ShZ.js.map} +1 -1
  164. package/dist/rl.d.ts +186 -186
  165. package/dist/rl.d.ts.map +1 -1
  166. package/dist/rl.js +21 -17
  167. package/dist/rl.js.map +1 -1
  168. package/dist/rollout/index.d.ts +1 -1
  169. package/dist/rollout/index.js +5 -5
  170. package/dist/{rollout-B-XBReTC.js → rollout-BUQixCoc.js} +5 -5
  171. package/dist/{rollout-B-XBReTC.js.map → rollout-BUQixCoc.js.map} +1 -1
  172. package/dist/{rubric-predictive-validity-CCK-1B7w.js → rubric-predictive-validity-B0Pg-3I5.js} +3 -3
  173. package/dist/{rubric-predictive-validity-CCK-1B7w.js.map → rubric-predictive-validity-B0Pg-3I5.js.map} +1 -1
  174. package/dist/{run-record-tNnlQIrD.js → run-record-BB290Y9A.js} +5 -3
  175. package/dist/{run-record-tNnlQIrD.js.map → run-record-BB290Y9A.js.map} +1 -1
  176. package/dist/{schema-C1aaAxTf.js → schema-DAMp77zB.js} +24 -20
  177. package/dist/{schema-C1aaAxTf.js.map → schema-DAMp77zB.js.map} +1 -1
  178. package/dist/search/index.d.ts +34 -34
  179. package/dist/search/index.d.ts.map +1 -1
  180. package/dist/search/index.js +2 -2
  181. package/dist/{search-kernel-AVgIjXaI.js → search-kernel-Dpniloks.js} +33 -26
  182. package/dist/{search-kernel-AVgIjXaI.js.map → search-kernel-Dpniloks.js.map} +1 -1
  183. package/dist/{search-ledger-CL0-mXQ8.js → search-ledger-CXQPwdWz.js} +4 -3
  184. package/dist/{search-ledger-CL0-mXQ8.js.map → search-ledger-CXQPwdWz.js.map} +1 -1
  185. package/dist/{search-shipper-Cs6K4km9.js → search-shipper-DhhofkDX.js} +3 -6
  186. package/dist/{search-shipper-Cs6K4km9.js.map → search-shipper-DhhofkDX.js.map} +1 -1
  187. package/dist/{search-summary-DvWk0hb3.js → search-summary-C1nAHgkB.js} +4 -4
  188. package/dist/{search-summary-DvWk0hb3.js.map → search-summary-C1nAHgkB.js.map} +1 -1
  189. package/dist/{semantic-concept-judge-2SE_kXGF.js → semantic-concept-judge-BcjYg37z.js} +4 -4
  190. package/dist/{semantic-concept-judge-2SE_kXGF.js.map → semantic-concept-judge-BcjYg37z.js.map} +1 -1
  191. package/dist/{sequential-Yz3OSskl.js → sequential-DWn_np6P.js} +2 -3
  192. package/dist/{sequential-Yz3OSskl.js.map → sequential-DWn_np6P.js.map} +1 -1
  193. package/dist/{server-D56r1lhp.js → server-B4jMhqQn.js} +2 -2
  194. package/dist/{server-D56r1lhp.js.map → server-B4jMhqQn.js.map} +1 -1
  195. package/dist/{skillopt-optimization-method-B2syNik0.js → skillopt-optimization-method-Bls9t-G_.js} +9 -9
  196. package/dist/{skillopt-optimization-method-B2syNik0.js.map → skillopt-optimization-method-Bls9t-G_.js.map} +1 -1
  197. package/dist/{statistical-heldout-BAFDCo07.js → statistical-heldout-CNLOvRq_.js} +3 -3
  198. package/dist/{statistical-heldout-BAFDCo07.js.map → statistical-heldout-CNLOvRq_.js.map} +1 -1
  199. package/dist/statistical-heldout-HswMvzbP.d.ts.map +1 -1
  200. package/dist/{store-DNe_Uv1Q.js → store-BhNuLdJw.js} +3 -5
  201. package/dist/{store-DNe_Uv1Q.js.map → store-BhNuLdJw.js.map} +1 -1
  202. package/dist/store-C4avFjIi.d.ts.map +1 -1
  203. package/dist/{store-otlp-BPowgPm6.js → store-otlp-yd9lDziN.js} +2 -2
  204. package/dist/{store-otlp-BPowgPm6.js.map → store-otlp-yd9lDziN.js.map} +1 -1
  205. package/dist/{store-tool-spans-BaGakFc2.js → store-tool-spans-Biio4_Nw.js} +4 -4
  206. package/dist/{store-tool-spans-BaGakFc2.js.map → store-tool-spans-Biio4_Nw.js.map} +1 -1
  207. package/dist/storyboard/index.d.ts +20 -21
  208. package/dist/storyboard/index.d.ts.map +1 -1
  209. package/dist/{student-t-BA-Uy51p.js → student-t-BBZYka0E.js} +4 -3
  210. package/dist/{student-t-BA-Uy51p.js.map → student-t-BBZYka0E.js.map} +1 -1
  211. package/dist/{summary-report-BeYnvddy.js → summary-report-DLzptLSA.js} +8 -7
  212. package/dist/{summary-report-BeYnvddy.js.map → summary-report-DLzptLSA.js.map} +1 -1
  213. package/dist/supervisor-run/index.d.ts +25 -25
  214. package/dist/supervisor-run/index.d.ts.map +1 -1
  215. package/dist/supervisor-run/index.js +2 -2
  216. package/dist/{tool-waste-CJDfxH45.js → tool-waste-B-u3rzCz.js} +11 -6
  217. package/dist/{tool-waste-CJDfxH45.js.map → tool-waste-B-u3rzCz.js.map} +1 -1
  218. package/dist/trace-repair/index.d.ts +96 -96
  219. package/dist/trace-repair/index.d.ts.map +1 -1
  220. package/dist/trace-repair/index.js +1 -1
  221. package/dist/trace-repair/index.js.map +1 -1
  222. package/dist/traces.d.ts +29 -29
  223. package/dist/traces.d.ts.map +1 -1
  224. package/dist/traces.js +9 -7
  225. package/dist/traces.js.map +1 -1
  226. package/dist/trajectory-replay/index.d.ts +35 -35
  227. package/dist/trajectory-replay/index.d.ts.map +1 -1
  228. package/dist/trajectory-replay/index.js +5 -5
  229. package/dist/trajectory-replay/index.js.map +1 -1
  230. package/dist/{tree-Ki_gW7z8.js → tree-D48IJUK0.js} +39 -31
  231. package/dist/{tree-Ki_gW7z8.js.map → tree-D48IJUK0.js.map} +1 -1
  232. package/dist/wire/index.d.ts +48 -48
  233. package/dist/wire/index.d.ts.map +1 -1
  234. package/dist/wire/index.js +1 -1
  235. package/docs/evaluator-construction.md +71 -0
  236. package/package.json +18 -36
  237. package/dist/campaign-L391bmir.js.map +0 -1
  238. package/dist/define-agent-eval-1YuvJpOs.d.ts.map +0 -1
  239. package/dist/index-D4pC7Tvd2.d.ts.map +0 -1
  240. package/dist/index-DKmrHA02.d.ts.map +0 -1
  241. package/dist/index-DNgf5gyG2.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ast-CP9ae9B0.js","names":[],"sources":["../src/experiment/ast.ts"],"sourcesContent":["/**\n * The registered-rule AST: every rule an experiment registers is DATA.\n *\n * A closure cannot be canonicalized or hashed; a node tree can. `sealExperiment`\n * hashes the whole tree, and every interpreter in this file takes only a node\n * plus evidence records — no parameter for alpha, threshold, metric, or\n * stopping rule exists on any executable surface. The registered object and\n * the executed object are therefore the same object, and registered-vs-ran\n * drift is unrepresentable rather than checked.\n *\n * Node families:\n * Predicate closed-key comparisons — the only leaf\n * AdmissionRule monotone funnel stages with registered waivers\n * SelectionRule deterministic subsets over a closed field set\n * Estimand what the experiment measures\n * IntervalSpec how uncertainty is computed, seed included\n * Condition decision guards over named derived quantities\n * DecisionRule ordered verdict table, or a registered absence of one\n * Obligation a control that must exist before a verdict class is read\n * ValidityGate pre-spend design checks\n * HaltRule gates as prerequisites — failure refuses the spend\n * BudgetRule spend schedules with a named ledger\n * MatchedBudgetRule arm budget matching as a refusal\n * ReissuePolicy carrier faults are reissued; model outcomes stand\n */\n\nimport { ValidationError } from '../errors'\nimport { mulberry32 } from '../statistics'\n\n/** JSON-serializable value — everything a sealed node may carry. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue }\n\n/** Evidence row shape. Fields are addressed by dot-separated paths. */\nexport type EvidenceRecord = Record<string, unknown>\n\n/** A decision rule's branches did not cover the evidence. */\nclass DecisionTableNotTotalError extends ValidationError {}\n\n// ── Predicate ────────────────────────────────────────────────────────\n\n/**\n * Closed-key comparison over a declared record schema. The only leaf node.\n * `field` is a dot-separated path into an evidence record.\n */\nexport type Predicate =\n | {\n kind: 'compare'\n field: string\n op: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte'\n value: JsonValue\n }\n | { kind: 'in'; field: string; values: JsonValue[] }\n | { kind: 'all'; of: Predicate[] }\n | { kind: 'any'; of: Predicate[] }\n | { kind: 'not'; of: Predicate }\n\n/** Read a dot-separated field path. Missing segments yield `undefined`. */\nexport function readField(record: EvidenceRecord, path: string): unknown {\n let current: unknown = record\n for (const key of path.split('.')) {\n if (current === null || typeof current !== 'object') return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n}\n\n/** Evaluate a predicate against one evidence record. */\nexport function evaluatePredicate(predicate: Predicate, record: EvidenceRecord): boolean {\n switch (predicate.kind) {\n case 'compare':\n return compareValues(readField(record, predicate.field), predicate.op, predicate.value)\n case 'in':\n return predicate.values.includes(readField(record, predicate.field) as JsonValue)\n case 'all':\n return predicate.of.every((p) => evaluatePredicate(p, record))\n case 'any':\n return predicate.of.some((p) => evaluatePredicate(p, record))\n case 'not':\n return !evaluatePredicate(predicate.of, record)\n }\n}\n\n/** Numbers compare numerically; everything else compares as strings. */\nfunction compareValues(\n value: unknown,\n op: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte',\n target: JsonValue,\n): boolean {\n if (op === 'eq') return value === target\n if (op === 'ne') return value !== target\n const numeric = typeof value === 'number' && typeof target === 'number'\n const left = numeric ? (value as number) : String(value)\n const right = numeric ? (target as number) : String(target)\n switch (op) {\n case 'lt':\n return left < right\n case 'lte':\n return left <= right\n case 'gt':\n return left > right\n case 'gte':\n return left >= right\n }\n}\n\n// ── Admission ────────────────────────────────────────────────────────\n\n/**\n * One monotone funnel stage. `waives` names substrate admission conditions\n * deliberately NOT applied, so a waiver is registered, never implicit.\n */\nexport interface AdmissionStage {\n id: string\n keep: Predicate\n waives?: string[]\n}\n\n/** A partition is a set reported separately and never pooled. */\nexport interface AdmissionPartition {\n id: string\n /** Stage whose dropped rows this partition draws from. */\n from: string\n keep: Predicate\n pooling: 'never'\n}\n\n/** Declarative row-admission funnel. Stages only remove rows. */\nexport interface AdmissionRule {\n population: string\n stages: AdmissionStage[]\n partitions?: AdmissionPartition[]\n}\n\n// ── Selection ────────────────────────────────────────────────────────\n\n/**\n * Deterministic subset selection. `reads` is the closed field set the rule\n * may touch — outcome-contaminated selection is unrepresentable because an\n * outcome field is simply not in the list.\n */\nexport type SelectionRule =\n | {\n kind: 'round-robin'\n groupBy: string\n groupOrder: 'lex-asc'\n withinOrder: { field: string; dir: 'asc' | 'desc' }\n take: number\n reads: string[]\n }\n | {\n kind: 'filter-of'\n /** Name of the sealed subset or selection this rule filters. */\n base: string\n keep: Predicate\n order: { field: string; dir: 'asc' | 'desc' }\n }\n\n/**\n * Execute a selection rule.\n *\n * Round-robin walks groups in lexicographic order and takes ids in\n * within-group order until `take` ids are chosen. Filter-of keeps the ids of\n * `bases[rule.base]` whose record satisfies the predicate, in the registered\n * order. Ids absent from `records` are evaluated on their id alone (fields\n * derived from the id via `idFields`), so a sealed base outlives its source\n * records.\n */\nexport function runSelectionRule(\n rule: SelectionRule,\n records: readonly EvidenceRecord[],\n options: {\n /** Field carrying a row's identity. */\n idField: string\n /** Sealed or previously-computed subsets, by name. */\n bases?: Record<string, readonly string[]>\n /** Derive predicate-readable fields from a bare id when its record is absent. */\n idFields?: (id: string) => EvidenceRecord\n },\n): string[] {\n if (rule.kind === 'round-robin') {\n const allowed = new Set(rule.reads)\n for (const field of [rule.groupBy, rule.withinOrder.field]) {\n if (!allowed.has(field)) {\n throw new ValidationError(\n `runSelectionRule: round-robin reads '${field}' but its closed read set is [${rule.reads.join(', ')}]`,\n )\n }\n }\n const byGroup = new Map<string, string[]>()\n for (const record of records) {\n const group = String(readField(record, rule.groupBy))\n const id = String(readField(record, rule.withinOrder.field))\n const bucket = byGroup.get(group)\n if (bucket) bucket.push(id)\n else byGroup.set(group, [id])\n }\n for (const ids of byGroup.values()) {\n ids.sort()\n if (rule.withinOrder.dir === 'desc') ids.reverse()\n }\n const groups = [...byGroup.keys()].sort()\n const chosen: string[] = []\n let cursor = 0\n while (chosen.length < rule.take && groups.some((g) => byGroup.get(g)!.length > 0)) {\n const group = groups[cursor % groups.length]!\n const ids = byGroup.get(group)!\n if (ids.length > 0) chosen.push(ids.shift()!)\n cursor += 1\n }\n return chosen\n }\n\n const base = options.bases?.[rule.base]\n if (!base) {\n throw new ValidationError(`runSelectionRule: filter-of base '${rule.base}' was not provided`)\n }\n const index = new Map(records.map((r) => [String(readField(r, options.idField)), r]))\n const kept = base.filter((id) => {\n const record = index.get(id) ?? options.idFields?.(id)\n if (!record) {\n throw new ValidationError(\n `runSelectionRule: base id '${id}' has no record and no idFields derivation`,\n )\n }\n return evaluatePredicate(rule.keep, record)\n })\n const sorted = [...kept].sort()\n if (rule.order.dir === 'desc') sorted.reverse()\n return sorted\n}\n\n// ── Estimands ────────────────────────────────────────────────────────\n\n/** A named set of row identities, built from arm rows and an event predicate. */\nexport type SetExpr =\n | { kind: 'rows-where'; arm: string; event: Predicate }\n | { kind: 'intersect'; of: SetExpr[] }\n\n/**\n * What the experiment measures. Every estimand names the fields it reads, so\n * the sealed tree records the full data dependency of the number.\n */\nexport type Estimand =\n | { kind: 'rate'; event: Predicate; over: 'rollouts' }\n | { kind: 'rate-at-least-once'; event: Predicate; groupBy: string }\n | {\n kind: 'paired-mean-diff'\n armField: string\n treatment: string\n control: string\n pairBy: string\n /**\n * Field path to the per-row outcome. A boolean reads as 1 or 0, so a\n * binary pass/fail outcome gives the risk difference directly.\n */\n value: string\n /** A pair one arm did not answer contributes a difference of exactly zero. */\n missing: 'zero-diff'\n }\n | {\n kind: 'set-ratio'\n armField: string\n idField: string\n numerator: SetExpr\n denominator: SetExpr\n }\n\nexport interface EstimandResult {\n value: number\n numerator: number\n denominator: number\n}\n\nfunction evaluateSetExpr(\n expr: SetExpr,\n rows: readonly EvidenceRecord[],\n armField: string,\n idField: string,\n): Set<string> {\n if (expr.kind === 'rows-where') {\n const ids = new Set<string>()\n for (const row of rows) {\n if (String(readField(row, armField)) !== expr.arm) continue\n if (evaluatePredicate(expr.event, row)) ids.add(String(readField(row, idField)))\n }\n return ids\n }\n if (expr.of.length === 0) throw new ValidationError('computeEstimand: empty intersect')\n const sets = expr.of.map((e) => evaluateSetExpr(e, rows, armField, idField))\n const [first, ...rest] = sets\n const out = new Set<string>()\n for (const id of first!) {\n if (rest.every((s) => s.has(id))) out.add(id)\n }\n return out\n}\n\n/**\n * Read one registered outcome field as a number.\n *\n * A binary outcome reaches evidence as `true` or `false`. Its mean is the pass\n * rate and the mean of its paired differences is the risk difference, so\n * `true` reads as 1 and `false` as 0 — the same quantity a caller would\n * otherwise encode by hand, and the same reading in every interpreter here.\n * Every other type, and a non-finite number, is a measurement defect: it\n * rejects instead of poisoning the mean with `NaN` or a coerced zero.\n */\nfunction readNumericOutcome(raw: unknown, context: string, field: string, where: string): number {\n if (typeof raw === 'boolean') return raw ? 1 : 0\n if (typeof raw === 'number' && Number.isFinite(raw)) return raw\n throw new ValidationError(\n `${context}: value field '${field}' is not a finite number or a boolean on ${where}`,\n )\n}\n\n/** Compute an estimand over evidence rows. Pure; reads only registered fields. */\nexport function computeEstimand(\n estimand: Estimand,\n rows: readonly EvidenceRecord[],\n): EstimandResult {\n switch (estimand.kind) {\n case 'rate': {\n const numerator = rows.filter((r) => evaluatePredicate(estimand.event, r)).length\n if (rows.length === 0) throw new ValidationError('computeEstimand: rate over zero rows')\n return { value: numerator / rows.length, numerator, denominator: rows.length }\n }\n case 'rate-at-least-once': {\n const byGroup = new Map<string, boolean>()\n for (const row of rows) {\n const group = String(readField(row, estimand.groupBy))\n const hit = evaluatePredicate(estimand.event, row)\n byGroup.set(group, (byGroup.get(group) ?? false) || hit)\n }\n if (byGroup.size === 0) {\n throw new ValidationError('computeEstimand: rate-at-least-once over zero groups')\n }\n const numerator = [...byGroup.values()].filter(Boolean).length\n return { value: numerator / byGroup.size, numerator, denominator: byGroup.size }\n }\n case 'paired-mean-diff': {\n const byPair = new Map<string, { treatment?: number; control?: number }>()\n for (const row of rows) {\n const arm = String(readField(row, estimand.armField))\n if (arm !== estimand.treatment && arm !== estimand.control) continue\n const pair = String(readField(row, estimand.pairBy))\n const value = readNumericOutcome(\n readField(row, estimand.value),\n 'computeEstimand paired-mean-diff',\n estimand.value,\n `pair '${pair}'`,\n )\n const slot = byPair.get(pair) ?? {}\n if (arm === estimand.treatment) slot.treatment = value\n else slot.control = value\n byPair.set(pair, slot)\n }\n if (byPair.size === 0) {\n throw new ValidationError('computeEstimand: paired-mean-diff over zero pairs')\n }\n let sum = 0\n for (const slot of byPair.values()) {\n // missing: 'zero-diff' — an unanswered side contributes zero, never a drop\n sum += (slot.treatment ?? 0) - (slot.control ?? 0)\n }\n return { value: sum / byPair.size, numerator: sum, denominator: byPair.size }\n }\n case 'set-ratio': {\n const numeratorSet = evaluateSetExpr(\n estimand.numerator,\n rows,\n estimand.armField,\n estimand.idField,\n )\n const denominatorSet = evaluateSetExpr(\n estimand.denominator,\n rows,\n estimand.armField,\n estimand.idField,\n )\n if (denominatorSet.size === 0) {\n throw new ValidationError('computeEstimand: set-ratio denominator set is empty')\n }\n return {\n value: numeratorSet.size / denominatorSet.size,\n numerator: numeratorSet.size,\n denominator: denominatorSet.size,\n }\n }\n }\n}\n\n// ── Intervals ────────────────────────────────────────────────────────\n\n/** How uncertainty is computed. The seed is part of the registration. */\nexport type IntervalSpec =\n | {\n kind: 'cluster-bootstrap'\n clusterBy: string\n /** Registered numeric or boolean outcome field, including a prepared paired difference. */\n value: string\n resamples: number\n seed: number\n level: number\n method: 'percentile'\n }\n | { kind: 'clopper-pearson'; level: number }\n\nexport interface ComputedInterval {\n lower: number\n upper: number\n level: number\n}\n\n/** Shared by registration and direct execution so malformed intervals cannot produce evidence. */\nexport function intervalSpecProblems(spec: IntervalSpec): string[] {\n if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {\n return ['interval must be an object']\n }\n if (spec.kind !== 'cluster-bootstrap' && spec.kind !== 'clopper-pearson') {\n return ['interval kind must be cluster-bootstrap or clopper-pearson']\n }\n const problems: string[] = []\n if (!Number.isFinite(spec.level) || spec.level <= 0 || spec.level >= 1) {\n problems.push('level must be a finite number between zero and one')\n }\n if (spec.kind === 'cluster-bootstrap') {\n for (const field of ['clusterBy', 'value'] as const) {\n const path = spec[field]\n if (\n typeof path !== 'string' ||\n path.split('.').some((segment) => segment.length === 0 || segment.trim() !== segment)\n ) {\n problems.push(`${field} must be a nonempty dot-separated field path`)\n }\n }\n if (\n !Number.isSafeInteger(spec.resamples) ||\n spec.resamples < 1 ||\n spec.resamples > 0xffff_ffff\n ) {\n problems.push('resamples must be a positive integer within the supported array length')\n }\n if (!Number.isSafeInteger(spec.seed)) problems.push('seed must be a safe integer')\n if (spec.method !== 'percentile') problems.push('method must be percentile')\n }\n return problems\n}\n\n/**\n * Execute an interval spec.\n *\n * Cluster-bootstrap resamples whole clusters of its registered `value` field and\n * takes percentile bounds of the pooled mean. Clopper-Pearson computes the\n * exact binomial interval and requires `successes`/`trials` evidence instead\n * of rows.\n */\nexport function computeInterval(\n spec: IntervalSpec,\n evidence:\n | { kind: 'rows'; rows: readonly EvidenceRecord[] }\n | { kind: 'binomial'; successes: number; trials: number },\n): ComputedInterval {\n const problems = intervalSpecProblems(spec)\n if (problems.length > 0) throw new ValidationError(`computeInterval: ${problems.join('; ')}`)\n if (evidence === null || typeof evidence !== 'object' || Array.isArray(evidence)) {\n throw new ValidationError('computeInterval: evidence must be an object')\n }\n if (spec.kind === 'cluster-bootstrap') {\n if (evidence.kind !== 'rows') {\n throw new ValidationError('computeInterval: cluster-bootstrap requires row evidence')\n }\n if ('value' in evidence) {\n throw new ValidationError(\n 'computeInterval: register value in the interval spec, not row evidence',\n )\n }\n if (!Array.isArray(evidence.rows))\n throw new ValidationError('computeInterval: rows must be an array')\n const clusters = new Map<string, number[]>()\n for (const [index, row] of evidence.rows.entries()) {\n if (row === null || typeof row !== 'object' || Array.isArray(row)) {\n throw new ValidationError(`computeInterval: row ${index} must be an object`)\n }\n const cluster = readField(row, spec.clusterBy)\n if (\n !(typeof cluster === 'string' && cluster.length > 0 && cluster.trim() === cluster) &&\n !(typeof cluster === 'number' && Number.isFinite(cluster))\n ) {\n throw new ValidationError(\n `computeInterval: cluster field '${spec.clusterBy}' must be a nonempty string or finite number on row ${index}`,\n )\n }\n const clusterKey = `${typeof cluster}:${cluster}`\n const value = readNumericOutcome(\n readField(row, spec.value),\n 'computeInterval cluster-bootstrap',\n spec.value,\n `cluster '${cluster}'`,\n )\n const bucket = clusters.get(clusterKey)\n if (bucket) bucket.push(value)\n else clusters.set(clusterKey, [value])\n }\n const clusterValues = [...clusters.entries()]\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([, values]) => values)\n if (clusterValues.length < 2) {\n throw new ValidationError(\n `computeInterval: cluster-bootstrap needs >= 2 clusters, got ${clusterValues.length}`,\n )\n }\n const rng = mulberry32(spec.seed)\n const means = new Array<number>(spec.resamples)\n for (let draw = 0; draw < spec.resamples; draw++) {\n let sum = 0\n let count = 0\n for (let pick = 0; pick < clusterValues.length; pick++) {\n const cluster = clusterValues[Math.floor(rng() * clusterValues.length)]!\n for (const value of cluster) sum += value\n count += cluster.length\n }\n means[draw] = sum / count\n if (!Number.isFinite(means[draw])) {\n throw new ValidationError(\n 'computeInterval: cluster mean overflowed the finite numeric range',\n )\n }\n }\n means.sort((a, b) => a - b)\n const alpha = 1 - spec.level\n const lowerIndex = Math.floor((alpha / 2) * spec.resamples)\n const upperIndex = Math.min(spec.resamples - 1, Math.ceil((1 - alpha / 2) * spec.resamples) - 1)\n return {\n lower: means[lowerIndex]!,\n upper: means[Math.max(lowerIndex, upperIndex)]!,\n level: spec.level,\n }\n }\n\n if (evidence.kind !== 'binomial') {\n throw new ValidationError('computeInterval: clopper-pearson requires binomial evidence')\n }\n const { successes, trials } = evidence\n if (\n !Number.isSafeInteger(successes) ||\n !Number.isSafeInteger(trials) ||\n trials <= 0 ||\n successes < 0\n ) {\n throw new ValidationError(\n `computeInterval: clopper-pearson needs 0 <= successes <= trials, got ${successes}/${trials}`,\n )\n }\n if (successes > trials) {\n throw new ValidationError(\n `computeInterval: clopper-pearson successes ${successes} exceed trials ${trials}`,\n )\n }\n const alpha = 1 - spec.level\n const lower = successes === 0 ? 0 : binomialQuantile(successes, trials, alpha / 2, 'lower')\n const upper = successes === trials ? 1 : binomialQuantile(successes, trials, alpha / 2, 'upper')\n return { lower, upper, level: spec.level }\n}\n\n/**\n * Clopper-Pearson bound by bisection on the binomial tail. The lower bound is\n * the p with P(X >= successes | p) = alpha; the upper is the p with\n * P(X <= successes | p) = alpha. Deterministic, no special functions.\n */\nfunction binomialQuantile(\n successes: number,\n trials: number,\n alpha: number,\n side: 'lower' | 'upper',\n): number {\n const tail = (p: number): number => {\n // log-space binomial pmf accumulation for numeric stability\n let sum = 0\n for (let k = 0; k <= trials; k++) {\n const inTail = side === 'lower' ? k >= successes : k <= successes\n if (!inTail) continue\n sum += Math.exp(logBinomialPmf(k, trials, p))\n }\n return sum\n }\n let lo = 0\n let hi = 1\n for (let iter = 0; iter < 100; iter++) {\n const mid = (lo + hi) / 2\n if (tail(mid) < alpha) {\n if (side === 'lower') lo = mid\n else hi = mid\n } else {\n if (side === 'lower') hi = mid\n else lo = mid\n }\n }\n return (lo + hi) / 2\n}\n\nfunction logBinomialPmf(k: number, n: number, p: number): number {\n if (p <= 0) return k === 0 ? 0 : Number.NEGATIVE_INFINITY\n if (p >= 1) return k === n ? 0 : Number.NEGATIVE_INFINITY\n return logChoose(n, k) + k * Math.log(p) + (n - k) * Math.log(1 - p)\n}\n\nfunction logChoose(n: number, k: number): number {\n return logFactorial(n) - logFactorial(k) - logFactorial(n - k)\n}\n\nconst LOG_FACTORIAL_CACHE: number[] = [0]\nfunction logFactorial(n: number): number {\n for (let i = LOG_FACTORIAL_CACHE.length; i <= n; i++) {\n LOG_FACTORIAL_CACHE[i] = LOG_FACTORIAL_CACHE[i - 1]! + Math.log(i)\n }\n return LOG_FACTORIAL_CACHE[n]!\n}\n\n// ── Conditions and decisions ─────────────────────────────────────────\n\n/** Decision guards read only named derived quantities — never raw rows. */\nexport type Condition =\n | { kind: 'interval-excludes-zero'; interval: string; sign: 'positive' | 'negative' }\n | { kind: 'interval-includes-zero'; interval: string }\n | { kind: 'quantity-threshold'; quantity: string; op: 'gte' | 'lte' | 'gt' | 'lt'; value: number }\n | { kind: 'obligation-met'; obligation: string }\n | { kind: 'all'; of: Condition[] }\n | { kind: 'any'; of: Condition[] }\n | { kind: 'not'; of: Condition }\n\n/** The named quantities a decision rule may read. Nothing else reaches it. */\nexport interface DerivedQuantities {\n intervals: Record<string, { lower: number; upper: number }>\n quantities: Record<string, number>\n obligationsMet: Record<string, boolean>\n}\n\nexport function evaluateCondition(condition: Condition, evidence: DerivedQuantities): boolean {\n switch (condition.kind) {\n case 'interval-excludes-zero': {\n const interval = evidence.intervals[condition.interval]\n if (!interval) {\n throw new ValidationError(\n `evaluateCondition: interval '${condition.interval}' is not in the evidence`,\n )\n }\n const excludes = interval.lower > 0 || interval.upper < 0\n return excludes && (condition.sign === 'positive' ? interval.lower > 0 : interval.upper < 0)\n }\n case 'interval-includes-zero': {\n const interval = evidence.intervals[condition.interval]\n if (!interval) {\n throw new ValidationError(\n `evaluateCondition: interval '${condition.interval}' is not in the evidence`,\n )\n }\n return interval.lower <= 0 && interval.upper >= 0\n }\n case 'quantity-threshold': {\n const value = evidence.quantities[condition.quantity]\n if (value === undefined) {\n throw new ValidationError(\n `evaluateCondition: quantity '${condition.quantity}' is not in the evidence`,\n )\n }\n return compareValues(value, condition.op, condition.value)\n }\n case 'obligation-met':\n return evidence.obligationsMet[condition.obligation] === true\n case 'all':\n return condition.of.every((c) => evaluateCondition(c, evidence))\n case 'any':\n return condition.of.some((c) => evaluateCondition(c, evidence))\n case 'not':\n return !evaluateCondition(condition.of, evidence)\n }\n}\n\nexport interface DecisionBranch {\n when: Condition\n verdict: string\n report: string[]\n}\n\n/**\n * Ordered decision table: the first branch whose condition holds fires, and a\n * table no branch matches throws — a non-total registration is a defect, not\n * an implicit verdict. `report-only` registers the ABSENCE of a verdict\n * branch: the estimate and the per-row table are the finding, and the\n * registered meaning prose rides as non-executable interpretation data.\n */\nexport type DecisionRule =\n | { kind: 'table'; branches: DecisionBranch[] }\n | {\n kind: 'report-only'\n estimands: string[]\n intervals: string[]\n perRow: string[]\n interpretation?: { onQualitative: string; consequence: string }[]\n }\n\nexport interface DecisionOutcome {\n verdict: string\n report: string[]\n}\n\nexport function executeDecisionRule(\n rule: DecisionRule,\n evidence: DerivedQuantities,\n): DecisionOutcome {\n if (rule.kind === 'report-only') {\n return { verdict: 'report-only', report: [...rule.estimands, ...rule.intervals] }\n }\n for (const branch of rule.branches) {\n if (evaluateCondition(branch.when, evidence)) {\n return { verdict: branch.verdict, report: branch.report }\n }\n }\n throw new DecisionTableNotTotalError(\n 'executeDecisionRule: decision table is not total — no branch matched the evidence',\n )\n}\n\n/** A registered control that must exist before a class of verdicts is read. */\nexport interface Obligation {\n id: string\n appliesToVerdicts: string[]\n control: string\n}\n\n// ── Validity gates and halt ──────────────────────────────────────────\n\n/** Pre-spend design checks. Each returns pass/fail plus its evidence. */\nexport type ValidityGate =\n | {\n kind: 'oracle-determinism'\n unit: 'suite' | 'assertion'\n replicates: number\n maxFlipRate: number\n }\n | {\n kind: 'population-reproducibility'\n joinOn: string\n compare: string[]\n maxChangedRows: number\n }\n | { kind: 'provenance-assertion'; claim: Predicate }\n | {\n kind: 'power-floor'\n target: number\n /** Minimum worthwhile effect; must be represented exactly in effectGrid. */\n minimumEffect: number\n effectGrid: number[]\n sim: { trials: number; resamples: number; seed: number }\n }\n | { kind: 'identity'; field: 'served-model'; op: 'basename-eq'; onFail: 'abort' }\n\nexport interface GateResult {\n id: string\n passed: boolean\n evidence: JsonValue\n}\n\n/**\n * Replicate-flip counting over graded states. A state whose replicates split\n * between pass and fail is flipping; its flip rate is the minority share.\n */\nexport function evaluateOracleDeterminismGate(\n id: string,\n gate: Extract<ValidityGate, { kind: 'oracle-determinism' }>,\n repsByState: Record<string, readonly boolean[]>,\n): GateResult {\n const evidence: Record<string, JsonValue> = {}\n let passed = true\n for (const [state, reps] of Object.entries(repsByState)) {\n const passes = reps.filter(Boolean).length\n const flipRate = reps.length === 0 ? 0 : Math.min(passes, reps.length - passes) / reps.length\n evidence[state] = { passes, replicates: reps.length, flipRate }\n if (flipRate > gate.maxFlipRate) passed = false\n }\n return { id, passed, evidence }\n}\n\n/**\n * Join two population snapshots on `joinOn` and compare the registered fields.\n * Only rows present in both snapshots are compared; a presence change is a\n * different failure and needs its own gate.\n */\nexport function evaluatePopulationReproducibilityGate(\n id: string,\n gate: Extract<ValidityGate, { kind: 'population-reproducibility' }>,\n populations: { left: readonly EvidenceRecord[]; right: readonly EvidenceRecord[] },\n): GateResult {\n const rightByKey = new Map(populations.right.map((r) => [String(readField(r, gate.joinOn)), r]))\n const changed: string[] = []\n for (const left of populations.left) {\n const key = String(readField(left, gate.joinOn))\n const right = rightByKey.get(key)\n if (!right) continue\n const moved = gate.compare.filter((f) => readField(left, f) !== readField(right, f))\n if (moved.length > 0) {\n changed.push(\n `${key} ${moved.map((f) => `${f}:${String(readField(left, f))}->${String(readField(right, f))}`).join(' ')}`,\n )\n }\n }\n return { id, passed: changed.length <= gate.maxChangedRows, evidence: changed }\n}\n\n/** The registered claim about provenance must hold on the provenance record. */\nexport function evaluateProvenanceGate(\n id: string,\n gate: Extract<ValidityGate, { kind: 'provenance-assertion' }>,\n provenance: EvidenceRecord,\n): GateResult {\n const passed = evaluatePredicate(gate.claim, provenance)\n return { id, passed, evidence: { claimHolds: passed } }\n}\n\n/** Final path segment equality between the pinned and the served identity. */\nexport function evaluateIdentityGate(\n id: string,\n _gate: Extract<ValidityGate, { kind: 'identity' }>,\n identities: { pinned: string; served: string },\n): GateResult {\n const basename = (s: string): string => s.split('/').pop() ?? s\n const passed = basename(identities.pinned) === basename(identities.served)\n return {\n id,\n passed,\n evidence: { pinned: identities.pinned, served: identities.served, matched: passed },\n }\n}\n\n/**\n * The design's power at minimumEffect must reach the registered target.\n * The curve must cover the registered effect grid exactly — a curve\n * computed on a different grid is different evidence and is refused.\n */\nexport function evaluatePowerFloorGate(\n id: string,\n gate: Extract<ValidityGate, { kind: 'power-floor' }>,\n curve: readonly { effect: number; power: number }[],\n): GateResult {\n const problems = powerFloorProblems(gate)\n if (problems.length > 0) {\n throw new ValidationError(`evaluatePowerFloorGate: ${problems.join('; ')}`)\n }\n if (curve.some((point) => !Number.isFinite(point.power) || point.power < 0 || point.power > 1)) {\n throw new ValidationError('evaluatePowerFloorGate: powers must be finite and in [0,1]')\n }\n const byEffect = new Map(curve.map((point) => [point.effect, point.power]))\n if (byEffect.size !== curve.length) {\n throw new ValidationError('evaluatePowerFloorGate: curve contains duplicate effects')\n }\n const missing = gate.effectGrid.filter((effect) => !byEffect.has(effect))\n if (missing.length > 0) {\n throw new ValidationError(\n `evaluatePowerFloorGate: curve does not cover registered effects [${missing.join(', ')}]`,\n )\n }\n const extra = curve.filter((point) => !gate.effectGrid.includes(point.effect))\n if (extra.length > 0) {\n throw new ValidationError(\n `evaluatePowerFloorGate: curve contains unregistered effects [${extra.map((point) => point.effect).join(', ')}]`,\n )\n }\n const powers = gate.effectGrid.map((effect) => byEffect.get(effect)!)\n const maxPower = Math.max(...powers)\n const powerAtMinimumEffect = byEffect.get(gate.minimumEffect)!\n return {\n id,\n passed: powerAtMinimumEffect >= gate.target,\n evidence: {\n target: gate.target,\n minimumEffect: gate.minimumEffect,\n powerAtMinimumEffect,\n maxPower,\n curve: gate.effectGrid.map((effect) => ({ effect, power: byEffect.get(effect)! })),\n },\n }\n}\n\n/** Shared by seal validation and direct execution; malformed designs never pass. */\nexport function powerFloorProblems(gate: Extract<ValidityGate, { kind: 'power-floor' }>): string[] {\n const problems: string[] = []\n if (!Number.isFinite(gate.target) || gate.target <= 0 || gate.target > 1) {\n problems.push('power target must be in (0,1]')\n }\n if (!Number.isFinite(gate.minimumEffect) || gate.minimumEffect <= 0) {\n problems.push('minimumEffect must be positive and finite')\n }\n if (gate.effectGrid.length === 0 || gate.effectGrid.some((effect) => !Number.isFinite(effect))) {\n problems.push('effectGrid must contain finite effects')\n }\n if (new Set(gate.effectGrid).size !== gate.effectGrid.length) {\n problems.push('effectGrid contains duplicate effects')\n }\n if (!gate.effectGrid.includes(gate.minimumEffect)) {\n problems.push('effectGrid must contain minimumEffect exactly; no interpolation is assumed')\n }\n for (const field of ['trials', 'resamples'] as const) {\n if (!Number.isInteger(gate.sim[field]) || gate.sim[field] <= 0) {\n problems.push(`power simulation ${field} must be a positive integer`)\n }\n }\n if (!Number.isInteger(gate.sim.seed)) problems.push('power simulation seed must be an integer')\n return problems\n}\n\n/** Checks as prerequisites: any named gate failing refuses the spend. */\nexport interface HaltRule {\n when: { kind: 'any-gate-failed'; gates: string[] }\n action: 'refuse-spend'\n report: 'settling-n'\n}\n\nexport interface HaltOutcome {\n fired: boolean\n action: 'refuse-spend' | null\n failedGates: string[]\n}\n\nexport function evaluateHaltRule(halt: HaltRule, gates: readonly GateResult[]): HaltOutcome {\n const seen = new Map(gates.map((g) => [g.id, g]))\n const missing = halt.when.gates.filter((id) => !seen.has(id))\n if (missing.length > 0) {\n throw new ValidationError(\n `evaluateHaltRule: halt references gates that were not evaluated: [${missing.join(', ')}]`,\n )\n }\n const failed = halt.when.gates.filter((id) => !seen.get(id)!.passed)\n return failed.length > 0\n ? { fired: true, action: halt.action, failedGates: failed }\n : { fired: false, action: null, failedGates: [] }\n}\n\n// ── Budget rules ─────────────────────────────────────────────────────\n\n/**\n * Spend schedules as data. `ledger` names every entry the ceiling counts, so\n * what the gate reads is registered, not improvised at gate time.\n */\nexport type BudgetRule =\n | {\n kind: 'uniform-pass'\n ceilingUsd: number\n maxPasses: number\n projection: 'last-pass-cost'\n costSource: 'priced-per-call'\n ledger: { id: string; usd: number }[]\n partialPass: 'report-never-lift'\n }\n | {\n kind: 'n-ladder'\n steps: number[]\n ceilingUsd: number\n projection: 'unit-cost-times-rows-times-n'\n onExhaust: 'refuse-report-projection'\n }\n\nexport interface UniformPassDecision {\n pass: number\n cumulativeBefore: number\n projected: number\n go: boolean\n}\n\nexport interface UniformPassSchedule {\n decisions: UniformPassDecision[]\n uniformN: number\n}\n\n/**\n * Execute the uniform-pass schedule against measured pass costs. Pass 1 always\n * runs; each later pass runs only when the cumulative spend plus the last\n * measured pass cost stays at or under the registered ceiling. The registered\n * ledger is the pre-spend the ceiling counts.\n */\nexport function runUniformPassBudget(\n rule: Extract<BudgetRule, { kind: 'uniform-pass' }>,\n measuredPassCosts: readonly number[],\n): UniformPassSchedule {\n const preSpend = rule.ledger.reduce((sum, entry) => sum + entry.usd, 0)\n let cumulative = preSpend\n const decisions: UniformPassDecision[] = []\n let uniformN = 0\n for (let pass = 1; pass <= rule.maxPasses; pass++) {\n if (pass === 1) {\n if (measuredPassCosts[0] === undefined) break\n cumulative += measuredPassCosts[0]\n uniformN = 1\n continue\n }\n const projected = measuredPassCosts[pass - 2]\n if (projected === undefined) break\n const go = cumulative + projected <= rule.ceilingUsd\n decisions.push({ pass, cumulativeBefore: cumulative, projected, go })\n if (!go || measuredPassCosts[pass - 1] === undefined) break\n cumulative += measuredPassCosts[pass - 1]!\n uniformN = pass\n }\n return { decisions, uniformN }\n}\n\nexport interface NLadderProjection {\n chosenN: number | null\n projections: { n: number; projectedUsd: number; affordable: boolean }[]\n refusal: null | { onExhaust: 'refuse-report-projection'; reason: string }\n}\n\n/**\n * Walk the registered n-ladder and pick the first affordable step. When no\n * step fits the ceiling, the rule refuses and reports the projection instead\n * of shrinking the row set — \"never subset rows\" is the registered invariant.\n */\nexport function projectNLadderBudget(\n rule: Extract<BudgetRule, { kind: 'n-ladder' }>,\n measured: { unitCostUsd: number; rows: number },\n): NLadderProjection {\n const projections = rule.steps.map((n) => {\n const projectedUsd = measured.unitCostUsd * measured.rows * n\n return { n, projectedUsd, affordable: projectedUsd <= rule.ceilingUsd }\n })\n const first = projections.find((p) => p.affordable)\n if (first) return { chosenN: first.n, projections, refusal: null }\n return {\n chosenN: null,\n projections,\n refusal: {\n onExhaust: rule.onExhaust,\n reason: `no ladder step fits the ${rule.ceilingUsd} USD ceiling at ${measured.rows} rows x ${measured.unitCostUsd} USD per unit`,\n },\n }\n}\n\n// ── Matched budget and reissue ───────────────────────────────────────\n\n/** Arm budget matching as a refusal, not prose. Verified by `verifyMatchedBudgets`. */\nexport interface MatchedBudgetRule {\n measure: 'realized-tokens'\n tolerance: number\n onFail: 'refuse-contrast'\n}\n\n/** Carrier faults are reissued; model outcomes stand. Closed enumeration. */\nexport interface ReissuePolicy {\n carrierEvents: ('http-status' | 'transport-error' | 'deadline' | 'empty-content')[]\n modelOutcomesStand: true\n maxIssues: number\n}\n\nexport type ReissueVerdict = 'reissue' | 'stands' | 'exhausted'\n\n/**\n * Classify one rollout event under the registered reissue policy. A carrier\n * event within the issue budget is reissued; a model outcome always stands;\n * a carrier event past `maxIssues` is exhausted and reported, never retried.\n */\nexport function classifyReissue(\n policy: ReissuePolicy,\n event: string,\n issuesSoFar: number,\n): ReissueVerdict {\n if (!(policy.carrierEvents as string[]).includes(event)) return 'stands'\n return issuesSoFar < policy.maxIssues ? 'reissue' : 'exhausted'\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ast-DJ6Bc7ab.js","names":[],"sources":["../src/experiment/ast.ts"],"sourcesContent":["/**\n * The registered-rule AST: every rule an experiment registers is DATA.\n *\n * A closure cannot be canonicalized or hashed; a node tree can. `sealExperiment`\n * hashes the whole tree, and every interpreter in this file takes only a node\n * plus evidence records — no parameter for alpha, threshold, metric, or\n * stopping rule exists on any executable surface. The registered object and\n * the executed object are therefore the same object, and registered-vs-ran\n * drift is unrepresentable rather than checked.\n *\n * Node families:\n * Predicate closed-key comparisons — the only leaf\n * AdmissionRule monotone funnel stages with registered waivers\n * SelectionRule deterministic subsets over a closed field set\n * Estimand what the experiment measures\n * IntervalSpec how uncertainty is computed, seed included\n * Condition decision guards over named derived quantities\n * DecisionRule ordered verdict table, or a registered absence of one\n * Obligation a control that must exist before a verdict class is read\n * ValidityGate pre-spend design checks\n * HaltRule gates as prerequisites — failure refuses the spend\n * BudgetRule spend schedules with a named ledger\n * MatchedBudgetRule arm budget matching as a refusal\n * ReissuePolicy carrier faults are reissued; model outcomes stand\n */\n\nimport { ValidationError } from '../errors'\nimport { mulberry32 } from '../statistics'\n\n/** JSON-serializable value — everything a sealed node may carry. */\nexport type JsonValue = string | number | boolean | null | JsonValue[] | { [k: string]: JsonValue }\n\n/** Evidence row shape. Fields are addressed by dot-separated paths. */\nexport type EvidenceRecord = Record<string, unknown>\n\n/** A decision rule's branches did not cover the evidence. */\nclass DecisionTableNotTotalError extends ValidationError {}\n\n// ── Predicate ────────────────────────────────────────────────────────\n\n/**\n * Closed-key comparison over a declared record schema. The only leaf node.\n * `field` is a dot-separated path into an evidence record.\n */\nexport type Predicate =\n | {\n kind: 'compare'\n field: string\n op: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte'\n value: JsonValue\n }\n | { kind: 'in'; field: string; values: JsonValue[] }\n | { kind: 'all'; of: Predicate[] }\n | { kind: 'any'; of: Predicate[] }\n | { kind: 'not'; of: Predicate }\n\n/** Read a dot-separated field path. Missing segments yield `undefined`. */\nexport function readField(record: EvidenceRecord, path: string): unknown {\n let current: unknown = record\n for (const key of path.split('.')) {\n if (current === null || typeof current !== 'object') return undefined\n current = (current as Record<string, unknown>)[key]\n }\n return current\n}\n\n/** Evaluate a predicate against one evidence record. */\nexport function evaluatePredicate(predicate: Predicate, record: EvidenceRecord): boolean {\n switch (predicate.kind) {\n case 'compare':\n return compareValues(readField(record, predicate.field), predicate.op, predicate.value)\n case 'in':\n return predicate.values.includes(readField(record, predicate.field) as JsonValue)\n case 'all':\n return predicate.of.every((p) => evaluatePredicate(p, record))\n case 'any':\n return predicate.of.some((p) => evaluatePredicate(p, record))\n case 'not':\n return !evaluatePredicate(predicate.of, record)\n }\n}\n\n/** Numbers compare numerically; everything else compares as strings. */\nfunction compareValues(\n value: unknown,\n op: 'eq' | 'ne' | 'lt' | 'lte' | 'gt' | 'gte',\n target: JsonValue,\n): boolean {\n if (op === 'eq') return value === target\n if (op === 'ne') return value !== target\n const numeric = typeof value === 'number' && typeof target === 'number'\n const left = numeric ? (value as number) : String(value)\n const right = numeric ? (target as number) : String(target)\n switch (op) {\n case 'lt':\n return left < right\n case 'lte':\n return left <= right\n case 'gt':\n return left > right\n case 'gte':\n return left >= right\n }\n}\n\n// ── Admission ────────────────────────────────────────────────────────\n\n/**\n * One monotone funnel stage. `waives` names substrate admission conditions\n * deliberately NOT applied, so a waiver is registered, never implicit.\n */\nexport interface AdmissionStage {\n id: string\n keep: Predicate\n waives?: string[]\n}\n\n/** A partition is a set reported separately and never pooled. */\nexport interface AdmissionPartition {\n id: string\n /** Stage whose dropped rows this partition draws from. */\n from: string\n keep: Predicate\n pooling: 'never'\n}\n\n/** Declarative row-admission funnel. Stages only remove rows. */\nexport interface AdmissionRule {\n population: string\n stages: AdmissionStage[]\n partitions?: AdmissionPartition[]\n}\n\n// ── Selection ────────────────────────────────────────────────────────\n\n/**\n * Deterministic subset selection. `reads` is the closed field set the rule\n * may touch — outcome-contaminated selection is unrepresentable because an\n * outcome field is simply not in the list.\n */\nexport type SelectionRule =\n | {\n kind: 'round-robin'\n groupBy: string\n groupOrder: 'lex-asc'\n withinOrder: { field: string; dir: 'asc' | 'desc' }\n take: number\n reads: string[]\n }\n | {\n kind: 'filter-of'\n /** Name of the sealed subset or selection this rule filters. */\n base: string\n keep: Predicate\n order: { field: string; dir: 'asc' | 'desc' }\n }\n\n/**\n * Execute a selection rule.\n *\n * Round-robin walks groups in lexicographic order and takes ids in\n * within-group order until `take` ids are chosen. Filter-of keeps the ids of\n * `bases[rule.base]` whose record satisfies the predicate, in the registered\n * order. Ids absent from `records` are evaluated on their id alone (fields\n * derived from the id via `idFields`), so a sealed base outlives its source\n * records.\n */\nexport function runSelectionRule(\n rule: SelectionRule,\n records: readonly EvidenceRecord[],\n options: {\n /** Field carrying a row's identity. */\n idField: string\n /** Sealed or previously-computed subsets, by name. */\n bases?: Record<string, readonly string[]>\n /** Derive predicate-readable fields from a bare id when its record is absent. */\n idFields?: (id: string) => EvidenceRecord\n },\n): string[] {\n if (rule.kind === 'round-robin') {\n const allowed = new Set(rule.reads)\n for (const field of [rule.groupBy, rule.withinOrder.field]) {\n if (!allowed.has(field)) {\n throw new ValidationError(\n `runSelectionRule: round-robin reads '${field}' but its closed read set is [${rule.reads.join(', ')}]`,\n )\n }\n }\n const byGroup = new Map<string, string[]>()\n for (const record of records) {\n const group = String(readField(record, rule.groupBy))\n const id = String(readField(record, rule.withinOrder.field))\n const bucket = byGroup.get(group)\n if (bucket) bucket.push(id)\n else byGroup.set(group, [id])\n }\n for (const ids of byGroup.values()) {\n ids.sort()\n if (rule.withinOrder.dir === 'desc') ids.reverse()\n }\n const groups = [...byGroup.keys()].sort()\n const chosen: string[] = []\n let cursor = 0\n while (chosen.length < rule.take && groups.some((g) => byGroup.get(g)!.length > 0)) {\n const group = groups[cursor % groups.length]!\n const ids = byGroup.get(group)!\n if (ids.length > 0) chosen.push(ids.shift()!)\n cursor += 1\n }\n return chosen\n }\n\n const base = options.bases?.[rule.base]\n if (!base) {\n throw new ValidationError(`runSelectionRule: filter-of base '${rule.base}' was not provided`)\n }\n const index = new Map(records.map((r) => [String(readField(r, options.idField)), r]))\n const kept = base.filter((id) => {\n const record = index.get(id) ?? options.idFields?.(id)\n if (!record) {\n throw new ValidationError(\n `runSelectionRule: base id '${id}' has no record and no idFields derivation`,\n )\n }\n return evaluatePredicate(rule.keep, record)\n })\n const sorted = [...kept].sort()\n if (rule.order.dir === 'desc') sorted.reverse()\n return sorted\n}\n\n// ── Estimands ────────────────────────────────────────────────────────\n\n/** A named set of row identities, built from arm rows and an event predicate. */\nexport type SetExpr =\n | { kind: 'rows-where'; arm: string; event: Predicate }\n | { kind: 'intersect'; of: SetExpr[] }\n\n/**\n * What the experiment measures. Every estimand names the fields it reads, so\n * the sealed tree records the full data dependency of the number.\n */\nexport type Estimand =\n | { kind: 'rate'; event: Predicate; over: 'rollouts' }\n | { kind: 'rate-at-least-once'; event: Predicate; groupBy: string }\n | {\n kind: 'paired-mean-diff'\n armField: string\n treatment: string\n control: string\n pairBy: string\n /**\n * Field path to the per-row outcome. A boolean reads as 1 or 0, so a\n * binary pass/fail outcome gives the risk difference directly.\n */\n value: string\n /** A pair one arm did not answer contributes a difference of exactly zero. */\n missing: 'zero-diff'\n }\n | {\n kind: 'set-ratio'\n armField: string\n idField: string\n numerator: SetExpr\n denominator: SetExpr\n }\n\nexport interface EstimandResult {\n value: number\n numerator: number\n denominator: number\n}\n\nfunction evaluateSetExpr(\n expr: SetExpr,\n rows: readonly EvidenceRecord[],\n armField: string,\n idField: string,\n): Set<string> {\n if (expr.kind === 'rows-where') {\n const ids = new Set<string>()\n for (const row of rows) {\n if (String(readField(row, armField)) !== expr.arm) continue\n if (evaluatePredicate(expr.event, row)) ids.add(String(readField(row, idField)))\n }\n return ids\n }\n if (expr.of.length === 0) throw new ValidationError('computeEstimand: empty intersect')\n const sets = expr.of.map((e) => evaluateSetExpr(e, rows, armField, idField))\n const [first, ...rest] = sets\n const out = new Set<string>()\n for (const id of first!) {\n if (rest.every((s) => s.has(id))) out.add(id)\n }\n return out\n}\n\n/**\n * Read one registered outcome field as a number.\n *\n * A binary outcome reaches evidence as `true` or `false`. Its mean is the pass\n * rate and the mean of its paired differences is the risk difference, so\n * `true` reads as 1 and `false` as 0 — the same quantity a caller would\n * otherwise encode by hand, and the same reading in every interpreter here.\n * Every other type, and a non-finite number, is a measurement defect: it\n * rejects instead of poisoning the mean with `NaN` or a coerced zero.\n */\nfunction readNumericOutcome(raw: unknown, context: string, field: string, where: string): number {\n if (typeof raw === 'boolean') return raw ? 1 : 0\n if (typeof raw === 'number' && Number.isFinite(raw)) return raw\n throw new ValidationError(\n `${context}: value field '${field}' is not a finite number or a boolean on ${where}`,\n )\n}\n\n/** Compute an estimand over evidence rows. Pure; reads only registered fields. */\nexport function computeEstimand(\n estimand: Estimand,\n rows: readonly EvidenceRecord[],\n): EstimandResult {\n switch (estimand.kind) {\n case 'rate': {\n const numerator = rows.filter((r) => evaluatePredicate(estimand.event, r)).length\n if (rows.length === 0) throw new ValidationError('computeEstimand: rate over zero rows')\n return { value: numerator / rows.length, numerator, denominator: rows.length }\n }\n case 'rate-at-least-once': {\n const byGroup = new Map<string, boolean>()\n for (const row of rows) {\n const group = String(readField(row, estimand.groupBy))\n const hit = evaluatePredicate(estimand.event, row)\n byGroup.set(group, (byGroup.get(group) ?? false) || hit)\n }\n if (byGroup.size === 0) {\n throw new ValidationError('computeEstimand: rate-at-least-once over zero groups')\n }\n const numerator = [...byGroup.values()].filter(Boolean).length\n return { value: numerator / byGroup.size, numerator, denominator: byGroup.size }\n }\n case 'paired-mean-diff': {\n const byPair = new Map<string, { treatment?: number; control?: number }>()\n for (const row of rows) {\n const arm = String(readField(row, estimand.armField))\n if (arm !== estimand.treatment && arm !== estimand.control) continue\n const pair = String(readField(row, estimand.pairBy))\n const value = readNumericOutcome(\n readField(row, estimand.value),\n 'computeEstimand paired-mean-diff',\n estimand.value,\n `pair '${pair}'`,\n )\n const slot = byPair.get(pair) ?? {}\n if (arm === estimand.treatment) slot.treatment = value\n else slot.control = value\n byPair.set(pair, slot)\n }\n if (byPair.size === 0) {\n throw new ValidationError('computeEstimand: paired-mean-diff over zero pairs')\n }\n let sum = 0\n for (const slot of byPair.values()) {\n // missing: 'zero-diff' — an unanswered side contributes zero, never a drop\n sum += (slot.treatment ?? 0) - (slot.control ?? 0)\n }\n return { value: sum / byPair.size, numerator: sum, denominator: byPair.size }\n }\n case 'set-ratio': {\n const numeratorSet = evaluateSetExpr(\n estimand.numerator,\n rows,\n estimand.armField,\n estimand.idField,\n )\n const denominatorSet = evaluateSetExpr(\n estimand.denominator,\n rows,\n estimand.armField,\n estimand.idField,\n )\n if (denominatorSet.size === 0) {\n throw new ValidationError('computeEstimand: set-ratio denominator set is empty')\n }\n return {\n value: numeratorSet.size / denominatorSet.size,\n numerator: numeratorSet.size,\n denominator: denominatorSet.size,\n }\n }\n }\n}\n\n// ── Intervals ────────────────────────────────────────────────────────\n\n/** How uncertainty is computed. The seed is part of the registration. */\nexport type IntervalSpec =\n | {\n kind: 'cluster-bootstrap'\n clusterBy: string\n /** Registered numeric or boolean outcome field, including a prepared paired difference. */\n value: string\n resamples: number\n seed: number\n level: number\n method: 'percentile'\n }\n | { kind: 'clopper-pearson'; level: number }\n\nexport interface ComputedInterval {\n lower: number\n upper: number\n level: number\n}\n\n/** Shared by registration and direct execution so malformed intervals cannot produce evidence. */\nexport function intervalSpecProblems(spec: IntervalSpec): string[] {\n if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {\n return ['interval must be an object']\n }\n if (spec.kind !== 'cluster-bootstrap' && spec.kind !== 'clopper-pearson') {\n return ['interval kind must be cluster-bootstrap or clopper-pearson']\n }\n const problems: string[] = []\n if (!Number.isFinite(spec.level) || spec.level <= 0 || spec.level >= 1) {\n problems.push('level must be a finite number between zero and one')\n }\n if (spec.kind === 'cluster-bootstrap') {\n for (const field of ['clusterBy', 'value'] as const) {\n const path = spec[field]\n if (\n typeof path !== 'string' ||\n path.split('.').some((segment) => segment.length === 0 || segment.trim() !== segment)\n ) {\n problems.push(`${field} must be a nonempty dot-separated field path`)\n }\n }\n if (\n !Number.isSafeInteger(spec.resamples) ||\n spec.resamples < 1 ||\n spec.resamples > 0xffff_ffff\n ) {\n problems.push('resamples must be a positive integer within the supported array length')\n }\n if (!Number.isSafeInteger(spec.seed)) problems.push('seed must be a safe integer')\n if (spec.method !== 'percentile') problems.push('method must be percentile')\n }\n return problems\n}\n\n/**\n * Execute an interval spec.\n *\n * Cluster-bootstrap resamples whole clusters of its registered `value` field and\n * takes percentile bounds of the pooled mean. Clopper-Pearson computes the\n * exact binomial interval and requires `successes`/`trials` evidence instead\n * of rows.\n */\nexport function computeInterval(\n spec: IntervalSpec,\n evidence:\n | { kind: 'rows'; rows: readonly EvidenceRecord[] }\n | { kind: 'binomial'; successes: number; trials: number },\n): ComputedInterval {\n const problems = intervalSpecProblems(spec)\n if (problems.length > 0) throw new ValidationError(`computeInterval: ${problems.join('; ')}`)\n if (evidence === null || typeof evidence !== 'object' || Array.isArray(evidence)) {\n throw new ValidationError('computeInterval: evidence must be an object')\n }\n if (spec.kind === 'cluster-bootstrap') {\n if (evidence.kind !== 'rows') {\n throw new ValidationError('computeInterval: cluster-bootstrap requires row evidence')\n }\n if ('value' in evidence) {\n throw new ValidationError(\n 'computeInterval: register value in the interval spec, not row evidence',\n )\n }\n if (!Array.isArray(evidence.rows))\n throw new ValidationError('computeInterval: rows must be an array')\n const clusters = new Map<string, number[]>()\n for (const [index, row] of evidence.rows.entries()) {\n if (row === null || typeof row !== 'object' || Array.isArray(row)) {\n throw new ValidationError(`computeInterval: row ${index} must be an object`)\n }\n const cluster = readField(row, spec.clusterBy)\n if (\n !(typeof cluster === 'string' && cluster.length > 0 && cluster.trim() === cluster) &&\n !(typeof cluster === 'number' && Number.isFinite(cluster))\n ) {\n throw new ValidationError(\n `computeInterval: cluster field '${spec.clusterBy}' must be a nonempty string or finite number on row ${index}`,\n )\n }\n const clusterKey = `${typeof cluster}:${cluster}`\n const value = readNumericOutcome(\n readField(row, spec.value),\n 'computeInterval cluster-bootstrap',\n spec.value,\n `cluster '${cluster}'`,\n )\n const bucket = clusters.get(clusterKey)\n if (bucket) bucket.push(value)\n else clusters.set(clusterKey, [value])\n }\n const clusterValues = [...clusters.entries()]\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([, values]) => values)\n if (clusterValues.length < 2) {\n throw new ValidationError(\n `computeInterval: cluster-bootstrap needs >= 2 clusters, got ${clusterValues.length}`,\n )\n }\n const rng = mulberry32(spec.seed)\n const means = new Array<number>(spec.resamples)\n for (let draw = 0; draw < spec.resamples; draw++) {\n let sum = 0\n let count = 0\n for (let pick = 0; pick < clusterValues.length; pick++) {\n const cluster = clusterValues[Math.floor(rng() * clusterValues.length)]!\n for (const value of cluster) sum += value\n count += cluster.length\n }\n means[draw] = sum / count\n if (!Number.isFinite(means[draw])) {\n throw new ValidationError(\n 'computeInterval: cluster mean overflowed the finite numeric range',\n )\n }\n }\n means.sort((a, b) => a - b)\n const alpha = 1 - spec.level\n const lowerIndex = Math.floor((alpha / 2) * spec.resamples)\n const upperIndex = Math.min(spec.resamples - 1, Math.ceil((1 - alpha / 2) * spec.resamples) - 1)\n return {\n lower: means[lowerIndex]!,\n upper: means[Math.max(lowerIndex, upperIndex)]!,\n level: spec.level,\n }\n }\n\n if (evidence.kind !== 'binomial') {\n throw new ValidationError('computeInterval: clopper-pearson requires binomial evidence')\n }\n const { successes, trials } = evidence\n if (\n !Number.isSafeInteger(successes) ||\n !Number.isSafeInteger(trials) ||\n trials <= 0 ||\n successes < 0\n ) {\n throw new ValidationError(\n `computeInterval: clopper-pearson needs 0 <= successes <= trials, got ${successes}/${trials}`,\n )\n }\n if (successes > trials) {\n throw new ValidationError(\n `computeInterval: clopper-pearson successes ${successes} exceed trials ${trials}`,\n )\n }\n const alpha = 1 - spec.level\n const lower = successes === 0 ? 0 : binomialQuantile(successes, trials, alpha / 2, 'lower')\n const upper = successes === trials ? 1 : binomialQuantile(successes, trials, alpha / 2, 'upper')\n return { lower, upper, level: spec.level }\n}\n\n/**\n * Clopper-Pearson bound by bisection on the binomial tail. The lower bound is\n * the p with P(X >= successes | p) = alpha; the upper is the p with\n * P(X <= successes | p) = alpha. Deterministic, no special functions.\n */\nfunction binomialQuantile(\n successes: number,\n trials: number,\n alpha: number,\n side: 'lower' | 'upper',\n): number {\n const tail = (p: number): number => {\n // log-space binomial pmf accumulation for numeric stability\n let sum = 0\n for (let k = 0; k <= trials; k++) {\n const inTail = side === 'lower' ? k >= successes : k <= successes\n if (!inTail) continue\n sum += Math.exp(logBinomialPmf(k, trials, p))\n }\n return sum\n }\n let lo = 0\n let hi = 1\n for (let iter = 0; iter < 100; iter++) {\n const mid = (lo + hi) / 2\n if (tail(mid) < alpha) {\n if (side === 'lower') lo = mid\n else hi = mid\n } else {\n if (side === 'lower') hi = mid\n else lo = mid\n }\n }\n return (lo + hi) / 2\n}\n\nfunction logBinomialPmf(k: number, n: number, p: number): number {\n if (p <= 0) return k === 0 ? 0 : Number.NEGATIVE_INFINITY\n if (p >= 1) return k === n ? 0 : Number.NEGATIVE_INFINITY\n return logChoose(n, k) + k * Math.log(p) + (n - k) * Math.log(1 - p)\n}\n\nfunction logChoose(n: number, k: number): number {\n return logFactorial(n) - logFactorial(k) - logFactorial(n - k)\n}\n\nconst LOG_FACTORIAL_CACHE: number[] = [0]\nfunction logFactorial(n: number): number {\n for (let i = LOG_FACTORIAL_CACHE.length; i <= n; i++) {\n LOG_FACTORIAL_CACHE[i] = LOG_FACTORIAL_CACHE[i - 1]! + Math.log(i)\n }\n return LOG_FACTORIAL_CACHE[n]!\n}\n\n// ── Conditions and decisions ─────────────────────────────────────────\n\n/** Decision guards read only named derived quantities — never raw rows. */\nexport type Condition =\n | { kind: 'interval-excludes-zero'; interval: string; sign: 'positive' | 'negative' }\n | { kind: 'interval-includes-zero'; interval: string }\n | { kind: 'quantity-threshold'; quantity: string; op: 'gte' | 'lte' | 'gt' | 'lt'; value: number }\n | { kind: 'obligation-met'; obligation: string }\n | { kind: 'all'; of: Condition[] }\n | { kind: 'any'; of: Condition[] }\n | { kind: 'not'; of: Condition }\n\n/** The named quantities a decision rule may read. Nothing else reaches it. */\nexport interface DerivedQuantities {\n intervals: Record<string, { lower: number; upper: number }>\n quantities: Record<string, number>\n obligationsMet: Record<string, boolean>\n}\n\nexport function evaluateCondition(condition: Condition, evidence: DerivedQuantities): boolean {\n switch (condition.kind) {\n case 'interval-excludes-zero': {\n const interval = evidence.intervals[condition.interval]\n if (!interval) {\n throw new ValidationError(\n `evaluateCondition: interval '${condition.interval}' is not in the evidence`,\n )\n }\n const excludes = interval.lower > 0 || interval.upper < 0\n return excludes && (condition.sign === 'positive' ? interval.lower > 0 : interval.upper < 0)\n }\n case 'interval-includes-zero': {\n const interval = evidence.intervals[condition.interval]\n if (!interval) {\n throw new ValidationError(\n `evaluateCondition: interval '${condition.interval}' is not in the evidence`,\n )\n }\n return interval.lower <= 0 && interval.upper >= 0\n }\n case 'quantity-threshold': {\n const value = evidence.quantities[condition.quantity]\n if (value === undefined) {\n throw new ValidationError(\n `evaluateCondition: quantity '${condition.quantity}' is not in the evidence`,\n )\n }\n return compareValues(value, condition.op, condition.value)\n }\n case 'obligation-met':\n return evidence.obligationsMet[condition.obligation] === true\n case 'all':\n return condition.of.every((c) => evaluateCondition(c, evidence))\n case 'any':\n return condition.of.some((c) => evaluateCondition(c, evidence))\n case 'not':\n return !evaluateCondition(condition.of, evidence)\n }\n}\n\nexport interface DecisionBranch {\n when: Condition\n verdict: string\n report: string[]\n}\n\n/**\n * Ordered decision table: the first branch whose condition holds fires, and a\n * table no branch matches throws — a non-total registration is a defect, not\n * an implicit verdict. `report-only` registers the ABSENCE of a verdict\n * branch: the estimate and the per-row table are the finding, and the\n * registered meaning prose rides as non-executable interpretation data.\n */\nexport type DecisionRule =\n | { kind: 'table'; branches: DecisionBranch[] }\n | {\n kind: 'report-only'\n estimands: string[]\n intervals: string[]\n perRow: string[]\n interpretation?: { onQualitative: string; consequence: string }[]\n }\n\nexport interface DecisionOutcome {\n verdict: string\n report: string[]\n}\n\nexport function executeDecisionRule(\n rule: DecisionRule,\n evidence: DerivedQuantities,\n): DecisionOutcome {\n if (rule.kind === 'report-only') {\n return { verdict: 'report-only', report: [...rule.estimands, ...rule.intervals] }\n }\n for (const branch of rule.branches) {\n if (evaluateCondition(branch.when, evidence)) {\n return { verdict: branch.verdict, report: branch.report }\n }\n }\n throw new DecisionTableNotTotalError(\n 'executeDecisionRule: decision table is not total — no branch matched the evidence',\n )\n}\n\n/** A registered control that must exist before a class of verdicts is read. */\nexport interface Obligation {\n id: string\n appliesToVerdicts: string[]\n control: string\n}\n\n// ── Validity gates and halt ──────────────────────────────────────────\n\n/** Pre-spend design checks. Each returns pass/fail plus its evidence. */\nexport type ValidityGate =\n | {\n kind: 'oracle-determinism'\n unit: 'suite' | 'assertion'\n replicates: number\n maxFlipRate: number\n }\n | {\n kind: 'population-reproducibility'\n joinOn: string\n compare: string[]\n maxChangedRows: number\n }\n | { kind: 'provenance-assertion'; claim: Predicate }\n | {\n kind: 'power-floor'\n target: number\n /** Minimum worthwhile effect; must be represented exactly in effectGrid. */\n minimumEffect: number\n effectGrid: number[]\n sim: { trials: number; resamples: number; seed: number }\n }\n | { kind: 'identity'; field: 'served-model'; op: 'basename-eq'; onFail: 'abort' }\n\nexport interface GateResult {\n id: string\n passed: boolean\n evidence: JsonValue\n}\n\n/**\n * Replicate-flip counting over graded states. A state whose replicates split\n * between pass and fail is flipping; its flip rate is the minority share.\n */\nexport function evaluateOracleDeterminismGate(\n id: string,\n gate: Extract<ValidityGate, { kind: 'oracle-determinism' }>,\n repsByState: Record<string, readonly boolean[]>,\n): GateResult {\n const evidence: Record<string, JsonValue> = {}\n let passed = true\n for (const [state, reps] of Object.entries(repsByState)) {\n const passes = reps.filter(Boolean).length\n const flipRate = reps.length === 0 ? 0 : Math.min(passes, reps.length - passes) / reps.length\n evidence[state] = { passes, replicates: reps.length, flipRate }\n if (flipRate > gate.maxFlipRate) passed = false\n }\n return { id, passed, evidence }\n}\n\n/**\n * Join two population snapshots on `joinOn` and compare the registered fields.\n * Only rows present in both snapshots are compared; a presence change is a\n * different failure and needs its own gate.\n */\nexport function evaluatePopulationReproducibilityGate(\n id: string,\n gate: Extract<ValidityGate, { kind: 'population-reproducibility' }>,\n populations: { left: readonly EvidenceRecord[]; right: readonly EvidenceRecord[] },\n): GateResult {\n const rightByKey = new Map(populations.right.map((r) => [String(readField(r, gate.joinOn)), r]))\n const changed: string[] = []\n for (const left of populations.left) {\n const key = String(readField(left, gate.joinOn))\n const right = rightByKey.get(key)\n if (!right) continue\n const moved = gate.compare.filter((f) => readField(left, f) !== readField(right, f))\n if (moved.length > 0) {\n changed.push(\n `${key} ${moved.map((f) => `${f}:${String(readField(left, f))}->${String(readField(right, f))}`).join(' ')}`,\n )\n }\n }\n return { id, passed: changed.length <= gate.maxChangedRows, evidence: changed }\n}\n\n/** The registered claim about provenance must hold on the provenance record. */\nexport function evaluateProvenanceGate(\n id: string,\n gate: Extract<ValidityGate, { kind: 'provenance-assertion' }>,\n provenance: EvidenceRecord,\n): GateResult {\n const passed = evaluatePredicate(gate.claim, provenance)\n return { id, passed, evidence: { claimHolds: passed } }\n}\n\n/** Final path segment equality between the pinned and the served identity. */\nexport function evaluateIdentityGate(\n id: string,\n _gate: Extract<ValidityGate, { kind: 'identity' }>,\n identities: { pinned: string; served: string },\n): GateResult {\n const basename = (s: string): string => s.split('/').pop() ?? s\n const passed = basename(identities.pinned) === basename(identities.served)\n return {\n id,\n passed,\n evidence: { pinned: identities.pinned, served: identities.served, matched: passed },\n }\n}\n\n/**\n * The design's power at minimumEffect must reach the registered target.\n * The curve must cover the registered effect grid exactly — a curve\n * computed on a different grid is different evidence and is refused.\n */\nexport function evaluatePowerFloorGate(\n id: string,\n gate: Extract<ValidityGate, { kind: 'power-floor' }>,\n curve: readonly { effect: number; power: number }[],\n): GateResult {\n const problems = powerFloorProblems(gate)\n if (problems.length > 0) {\n throw new ValidationError(`evaluatePowerFloorGate: ${problems.join('; ')}`)\n }\n if (curve.some((point) => !Number.isFinite(point.power) || point.power < 0 || point.power > 1)) {\n throw new ValidationError('evaluatePowerFloorGate: powers must be finite and in [0,1]')\n }\n const byEffect = new Map(curve.map((point) => [point.effect, point.power]))\n if (byEffect.size !== curve.length) {\n throw new ValidationError('evaluatePowerFloorGate: curve contains duplicate effects')\n }\n const missing = gate.effectGrid.filter((effect) => !byEffect.has(effect))\n if (missing.length > 0) {\n throw new ValidationError(\n `evaluatePowerFloorGate: curve does not cover registered effects [${missing.join(', ')}]`,\n )\n }\n const extra = curve.filter((point) => !gate.effectGrid.includes(point.effect))\n if (extra.length > 0) {\n throw new ValidationError(\n `evaluatePowerFloorGate: curve contains unregistered effects [${extra.map((point) => point.effect).join(', ')}]`,\n )\n }\n const powers = gate.effectGrid.map((effect) => byEffect.get(effect)!)\n const maxPower = Math.max(...powers)\n const powerAtMinimumEffect = byEffect.get(gate.minimumEffect)!\n return {\n id,\n passed: powerAtMinimumEffect >= gate.target,\n evidence: {\n target: gate.target,\n minimumEffect: gate.minimumEffect,\n powerAtMinimumEffect,\n maxPower,\n curve: gate.effectGrid.map((effect) => ({ effect, power: byEffect.get(effect)! })),\n },\n }\n}\n\n/** Shared by seal validation and direct execution; malformed designs never pass. */\nexport function powerFloorProblems(gate: Extract<ValidityGate, { kind: 'power-floor' }>): string[] {\n const problems: string[] = []\n if (!Number.isFinite(gate.target) || gate.target <= 0 || gate.target > 1) {\n problems.push('power target must be in (0,1]')\n }\n if (!Number.isFinite(gate.minimumEffect) || gate.minimumEffect <= 0) {\n problems.push('minimumEffect must be positive and finite')\n }\n if (gate.effectGrid.length === 0 || gate.effectGrid.some((effect) => !Number.isFinite(effect))) {\n problems.push('effectGrid must contain finite effects')\n }\n if (new Set(gate.effectGrid).size !== gate.effectGrid.length) {\n problems.push('effectGrid contains duplicate effects')\n }\n if (!gate.effectGrid.includes(gate.minimumEffect)) {\n problems.push('effectGrid must contain minimumEffect exactly; no interpolation is assumed')\n }\n for (const field of ['trials', 'resamples'] as const) {\n if (!Number.isInteger(gate.sim[field]) || gate.sim[field] <= 0) {\n problems.push(`power simulation ${field} must be a positive integer`)\n }\n }\n if (!Number.isInteger(gate.sim.seed)) problems.push('power simulation seed must be an integer')\n return problems\n}\n\n/** Checks as prerequisites: any named gate failing refuses the spend. */\nexport interface HaltRule {\n when: { kind: 'any-gate-failed'; gates: string[] }\n action: 'refuse-spend'\n report: 'settling-n'\n}\n\nexport interface HaltOutcome {\n fired: boolean\n action: 'refuse-spend' | null\n failedGates: string[]\n}\n\nexport function evaluateHaltRule(halt: HaltRule, gates: readonly GateResult[]): HaltOutcome {\n const seen = new Map(gates.map((g) => [g.id, g]))\n const missing = halt.when.gates.filter((id) => !seen.has(id))\n if (missing.length > 0) {\n throw new ValidationError(\n `evaluateHaltRule: halt references gates that were not evaluated: [${missing.join(', ')}]`,\n )\n }\n const failed = halt.when.gates.filter((id) => !seen.get(id)!.passed)\n return failed.length > 0\n ? { fired: true, action: halt.action, failedGates: failed }\n : { fired: false, action: null, failedGates: [] }\n}\n\n// ── Budget rules ─────────────────────────────────────────────────────\n\n/**\n * Spend schedules as data. `ledger` names every entry the ceiling counts, so\n * what the gate reads is registered, not improvised at gate time.\n */\nexport type BudgetRule =\n | {\n kind: 'uniform-pass'\n ceilingUsd: number\n maxPasses: number\n projection: 'last-pass-cost'\n costSource: 'priced-per-call'\n ledger: { id: string; usd: number }[]\n partialPass: 'report-never-lift'\n }\n | {\n kind: 'n-ladder'\n steps: number[]\n ceilingUsd: number\n projection: 'unit-cost-times-rows-times-n'\n onExhaust: 'refuse-report-projection'\n }\n\nexport interface UniformPassDecision {\n pass: number\n cumulativeBefore: number\n projected: number\n go: boolean\n}\n\nexport interface UniformPassSchedule {\n decisions: UniformPassDecision[]\n uniformN: number\n}\n\n/**\n * Execute the uniform-pass schedule against measured pass costs. Pass 1 always\n * runs; each later pass runs only when the cumulative spend plus the last\n * measured pass cost stays at or under the registered ceiling. The registered\n * ledger is the pre-spend the ceiling counts.\n */\nexport function runUniformPassBudget(\n rule: Extract<BudgetRule, { kind: 'uniform-pass' }>,\n measuredPassCosts: readonly number[],\n): UniformPassSchedule {\n const preSpend = rule.ledger.reduce((sum, entry) => sum + entry.usd, 0)\n let cumulative = preSpend\n const decisions: UniformPassDecision[] = []\n let uniformN = 0\n for (let pass = 1; pass <= rule.maxPasses; pass++) {\n if (pass === 1) {\n if (measuredPassCosts[0] === undefined) break\n cumulative += measuredPassCosts[0]\n uniformN = 1\n continue\n }\n const projected = measuredPassCosts[pass - 2]\n if (projected === undefined) break\n const go = cumulative + projected <= rule.ceilingUsd\n decisions.push({ pass, cumulativeBefore: cumulative, projected, go })\n if (!go || measuredPassCosts[pass - 1] === undefined) break\n cumulative += measuredPassCosts[pass - 1]!\n uniformN = pass\n }\n return { decisions, uniformN }\n}\n\nexport interface NLadderProjection {\n chosenN: number | null\n projections: { n: number; projectedUsd: number; affordable: boolean }[]\n refusal: null | { onExhaust: 'refuse-report-projection'; reason: string }\n}\n\n/**\n * Walk the registered n-ladder and pick the first affordable step. When no\n * step fits the ceiling, the rule refuses and reports the projection instead\n * of shrinking the row set — \"never subset rows\" is the registered invariant.\n */\nexport function projectNLadderBudget(\n rule: Extract<BudgetRule, { kind: 'n-ladder' }>,\n measured: { unitCostUsd: number; rows: number },\n): NLadderProjection {\n const projections = rule.steps.map((n) => {\n const projectedUsd = measured.unitCostUsd * measured.rows * n\n return { n, projectedUsd, affordable: projectedUsd <= rule.ceilingUsd }\n })\n const first = projections.find((p) => p.affordable)\n if (first) return { chosenN: first.n, projections, refusal: null }\n return {\n chosenN: null,\n projections,\n refusal: {\n onExhaust: rule.onExhaust,\n reason: `no ladder step fits the ${rule.ceilingUsd} USD ceiling at ${measured.rows} rows x ${measured.unitCostUsd} USD per unit`,\n },\n }\n}\n\n// ── Matched budget and reissue ───────────────────────────────────────\n\n/** Arm budget matching as a refusal, not prose. Verified by `verifyMatchedBudgets`. */\nexport interface MatchedBudgetRule {\n measure: 'realized-tokens'\n tolerance: number\n onFail: 'refuse-contrast'\n}\n\n/** Carrier faults are reissued; model outcomes stand. Closed enumeration. */\nexport interface ReissuePolicy {\n carrierEvents: ('http-status' | 'transport-error' | 'deadline' | 'empty-content')[]\n modelOutcomesStand: true\n maxIssues: number\n}\n\nexport type ReissueVerdict = 'reissue' | 'stands' | 'exhausted'\n\n/**\n * Classify one rollout event under the registered reissue policy. A carrier\n * event within the issue budget is reissued; a model outcome always stands;\n * a carrier event past `maxIssues` is exhausted and reported, never retried.\n */\nexport function classifyReissue(\n policy: ReissuePolicy,\n event: string,\n issuesSoFar: number,\n): ReissueVerdict {\n if (!(policy.carrierEvents as string[]).includes(event)) return 'stands'\n return issuesSoFar < policy.maxIssues ? 'reissue' : 'exhausted'\n}\n"],"mappings":"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"}
@@ -25,11 +25,11 @@
25
25
  * `extractProducedState(...).artifacts` yields, so any consumer can feed a run's
26
26
  * produced state straight in.
27
27
  */
28
- interface ProducedFile {
28
+ export interface ProducedFile {
29
29
  path: string;
30
30
  content?: string;
31
31
  }
32
- interface AuthenticitySignals {
32
+ export interface AuthenticitySignals {
33
33
  /** Human label for the domain (e.g. 'fhenix-fhe'). */
34
34
  label: string;
35
35
  /** A file the task REQUIRES (e.g. /\.sol$/ for an on-chain task). */
@@ -56,7 +56,7 @@ interface AuthenticitySignals {
56
56
  wiring?: number;
57
57
  };
58
58
  }
59
- interface AuthenticityResult {
59
+ export interface AuthenticityResult {
60
60
  /** Deterministic realness, 0 (BS) … 100 (real on real infra). */
61
61
  realness: number;
62
62
  requiredArtifactPresent: boolean;
@@ -79,19 +79,19 @@ interface AuthenticityResult {
79
79
  }
80
80
  /** Deterministic authenticity scan of produced files. Pure — same files in,
81
81
  * same score out. No LLM, no IO. */
82
- declare function scoreAuthenticity(files: readonly ProducedFile[], signals: AuthenticitySignals): AuthenticityResult;
83
- interface RealnessGate {
82
+ export declare function scoreAuthenticity(files: readonly ProducedFile[], signals: AuthenticitySignals): AuthenticityResult;
83
+ export interface RealnessGate {
84
84
  gated: boolean;
85
85
  reason?: string;
86
86
  }
87
87
  /** Anti-Goodhart gate: a required-artifact-missing or faked submission is
88
88
  * capped and cannot rank high regardless of buildability. */
89
- declare function gateRealness(r: AuthenticityResult, opts?: {
89
+ export declare function gateRealness(r: AuthenticityResult, opts?: {
90
90
  floor?: number;
91
91
  requireArtifact?: boolean;
92
92
  requireArtifactWired?: boolean;
93
93
  }): RealnessGate;
94
- interface AuthenticityNuance {
94
+ export interface AuthenticityNuance {
95
95
  /** 0 (nothing mocked) … 100 (entirely mocked). */
96
96
  mockedPct: number;
97
97
  /** 0 (genuine) … 100 (a hollow facade / cargo-culted). */
@@ -102,18 +102,18 @@ interface AuthenticityNuance {
102
102
  }
103
103
  /** A minimal completion fn — inject your model caller (router/tcloud). Keeps
104
104
  * this module free of any specific LLM client. */
105
- type CompleteFn = (system: string, user: string) => Promise<string>;
105
+ export type CompleteFn = (system: string, user: string) => Promise<string>;
106
106
  /**
107
107
  * LLM nuance scoring — judges the "looks real but is hollow" axis structure
108
108
  * misses. Inject a `complete` caller; returns mocked/fake/unique % + a verdict.
109
109
  * Fail-soft: a bad/unparseable response yields a worst-case (fully-fake) read,
110
110
  * never a false pass.
111
111
  */
112
- declare function scoreAuthenticityNuance(files: readonly ProducedFile[], complete: CompleteFn, opts?: {
112
+ export declare function scoreAuthenticityNuance(files: readonly ProducedFile[], complete: CompleteFn, opts?: {
113
113
  intent?: string;
114
114
  prioritize?: RegExp;
115
115
  }): Promise<AuthenticityNuance>;
116
- interface RealnessJudgment {
116
+ export interface RealnessJudgment {
117
117
  /** 0 (facade/simulator) … 100 (real implementation on the intended infra). */
118
118
  isReal: number;
119
119
  rationale: string;
@@ -126,13 +126,13 @@ interface RealnessJudgment {
126
126
  * consumer supplies `intent` (what the deliverable should be) and `rubric` (domain
127
127
  * specifics of real-vs-fake). Fail-closed: a bad response reads as fully fake.
128
128
  */
129
- declare function judgeRealnessLlm(files: readonly ProducedFile[], complete: CompleteFn, opts?: {
129
+ export declare function judgeRealnessLlm(files: readonly ProducedFile[], complete: CompleteFn, opts?: {
130
130
  intent?: string;
131
131
  rubric?: string;
132
132
  prioritize?: RegExp;
133
133
  }): Promise<RealnessJudgment>;
134
- type RealnessBand = 'clean-real' | 'clean-fake' | 'gray';
135
- interface BlendedRealness extends AuthenticityResult {
134
+ export type RealnessBand = 'clean-real' | 'clean-fake' | 'gray';
135
+ export interface BlendedRealness extends AuthenticityResult {
136
136
  /** Final realness after (only-when-needed) LLM adjudication, 0…100. */
137
137
  blendedRealness: number;
138
138
  band: RealnessBand;
@@ -152,12 +152,11 @@ interface BlendedRealness extends AuthenticityResult {
152
152
  * Domain-agnostic: the gray-band TRIGGER is structural; the LLM judges via the
153
153
  * consumer-supplied `intent`. Fail-closed (a bad LLM response reads as fully fake).
154
154
  */
155
- declare function scoreRealnessBlended(files: readonly ProducedFile[], signals: AuthenticitySignals, complete: CompleteFn, opts?: {
155
+ export declare function scoreRealnessBlended(files: readonly ProducedFile[], signals: AuthenticitySignals, complete: CompleteFn, opts?: {
156
156
  intent?: string;
157
157
  rubric?: string;
158
158
  grayBand?: [number, number];
159
159
  mockGrayThreshold?: number;
160
160
  }): Promise<BlendedRealness>;
161
161
  //#endregion
162
- export { AuthenticityNuance, AuthenticityResult, AuthenticitySignals, BlendedRealness, CompleteFn, ProducedFile, RealnessBand, RealnessGate, RealnessJudgment, gateRealness, judgeRealnessLlm, scoreAuthenticity, scoreAuthenticityNuance, scoreRealnessBlended };
163
162
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/authenticity/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;UA2BiB;EACf;EACA;;UAGe;;EAEf;;EAEA,mBAAmB;;EAEnB,WAAW;;;;EAIX,UAAU;;EAEV,WAAW;;EAEX,SAAS;;EAET,UAAU;;EAEV,OAAO;;EAEP;IAAY;IAAmB;IAAe;IAAgB;;;UAG/C;;EAEf;EACA;EACA;EACA;EACA;EACA;;;;EAIA;;;EAGA;EACA;;EAEA;;EAEA;;;;iBAoDc,kBACd,gBAAgB,gBAChB,SAAS,sBACR;UA8Fc;EACf;EACA;;;;iBAKc,aACd,GAAG,oBACH;EAAQ;EAAgB;EAA2B;IAClD;UA0Bc;;EAEf;;EAEA;;EAEA;EACA;;;;KAKU,cAAc,gBAAgB,iBAAiB;;;;;;;iBAgCrC,wBACpB,gBAAgB,gBAChB,UAAU,YACV;EAAQ;EAAiB,aAAa;IACrC,QAAQ;UAmCM;;EAEf;EACA;;;;;;;;;;iBAWoB,iBACpB,gBAAgB,gBAChB,UAAU,YACV;EAAQ;EAAiB;EAAiB,aAAa;IACtD,QAAQ;KAgCC;UAEK,wBAAwB;;EAEvC;EACA,MAAM;;EAEN;;EAEA,WAAW;;;;;;;;;;;;;iBAcS,qBACpB,gBAAgB,gBAChB,SAAS,qBACT,UAAU,YACV;EACE;EACA;EACA;EACA;IAED,QAAQ"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/authenticity/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2BiB;EACf;EACA;;iBAGe;;EAEf;;EAEA,mBAAmB;;EAEnB,WAAW;;;;EAIX,UAAU;;EAEV,WAAW;;EAEX,SAAS;;EAET,UAAU;;EAEV,OAAO;;EAEP;IAAY;IAAmB;IAAe;IAAgB;;;iBAG/C;;EAEf;EACA;EACA;EACA;EACA;EACA;;;;EAIA;;;EAGA;EACA;;EAEA;;EAEA;;;;wBAoDc,kBACd,gBAAgB,gBAChB,SAAS,sBACR;iBA8Fc;EACf;EACA;;;;wBAKc,aACd,GAAG,oBACH;EAAQ;EAAgB;EAA2B;IAClD;iBA0Bc;;EAEf;;EAEA;;EAEA;EACA;;;;YAKU,cAAc,gBAAgB,iBAAiB;;;;;;;wBAgCrC,wBACpB,gBAAgB,gBAChB,UAAU,YACV;EAAQ;EAAiB,aAAa;IACrC,QAAQ;iBAmCM;;EAEf;EACA;;;;;;;;;;wBAWoB,iBACpB,gBAAgB,gBAChB,UAAU,YACV;EAAQ;EAAiB;EAAiB,aAAa;IACtD,QAAQ;YAgCC;iBAEK,wBAAwB;;EAEvC;EACA,MAAM;;EAEN;;EAEA,WAAW;;;;;;;;;;;;;wBAcS,qBACpB,gBAAgB,gBAChB,SAAS,qBACT,UAAU,YACV;EACE;EACA;EACA;EACA;IAED,QAAQ"}
@@ -1,4 +1,4 @@
1
- import { n as studentTQuantile, t as studentTCdf } from "./student-t-BA-Uy51p.js";
1
+ import { n as studentTQuantile, t as studentTCdf } from "./student-t-BBZYka0E.js";
2
2
  import { n as cohensD } from "./effect-sizes-DiH8MGOH.js";
3
3
  //#region src/baseline.ts
4
4
  /**
@@ -147,4 +147,4 @@ function assertFiniteSample(name, sample) {
147
147
  //#endregion
148
148
  export { iqr as n, welchsTTest as r, compareToBaseline as t };
149
149
 
150
- //# sourceMappingURL=baseline-BC-eBZ7U.js.map
150
+ //# sourceMappingURL=baseline-CVKsfWTg.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"baseline-BC-eBZ7U.js","names":[],"sources":["../src/baseline.ts"],"sourcesContent":["/**\n * Baseline regression detection.\n *\n * Lifted from ADC baseline.ts. Every promotion-blocking signal boils down\n * to: \"is this run measurably worse than baseline?\" — with enough\n * statistical rigor to distinguish noise from drift.\n *\n * Uses:\n * - Welch's t-test (unequal variance) for per-metric mean comparison\n * - Cohen's d for effect size magnitude\n * - IQR for stability flag (unstable samples can't be trusted for comparisons)\n *\n * Returns a structured verdict: improved | regressed | stable | unstable.\n */\n\nimport { studentTCdf, studentTQuantile } from './math/student-t'\nimport { cohensD } from './statistics'\n\nexport interface MetricSamples {\n /** Stable metric key (e.g. \"overallScore\", \"firstTokenMs\"). */\n metric: string\n /** Whether higher values are better. */\n higherIsBetter: boolean\n baseline: number[]\n candidate: number[]\n}\n\nexport interface MetricVerdict {\n metric: string\n baselineMean: number\n candidateMean: number\n delta: number\n /** Null when both samples are constant with unequal means — the\n * standardized effect is unbounded there, not zero. */\n cohensD: number | null\n welchT: number\n welchDf: number\n welchP: number\n stable: boolean\n /** IQR of the combined samples — used as a rough stability indicator. */\n iqr: number\n verdict: 'improved' | 'regressed' | 'stable' | 'unstable'\n}\n\nexport interface BaselineReport {\n metrics: MetricVerdict[]\n /** True if any critical metric regressed. */\n hasRegression: boolean\n /** True if any metric is unstable (too noisy to judge). */\n hasUnstable: boolean\n}\n\nexport interface BaselineOptions {\n /** Effect size threshold for meaningful delta (default 0.5 — medium effect). */\n effectThreshold?: number\n /** p-value threshold for statistical significance (default 0.05). */\n alpha?: number\n /** IQR/mean ratio above which samples are flagged unstable (default 0.30). */\n unstableCvThreshold?: number\n}\n\nexport type WelchTestStatus = 'ok' | 'insufficient-sample' | 'zero-variance'\n\n/**\n * Full unequal-variance comparison for two independent samples.\n *\n * `meanB`, `delta`, `t`, and `cohensD` are oriented as `b - a`. Inferential\n * fields are `NaN` and `ci95` is null when the sample count or observed\n * variance cannot define a Student-t result. `status` makes that state\n * explicit so callers cannot mistake it for evidence of no difference.\n */\ninterface WelchTestResultBase {\n meanA: number\n meanB: number\n delta: number\n}\n\nexport type WelchTestResult =\n | (WelchTestResultBase & {\n status: 'ok'\n standardError: number\n t: number\n df: number\n p: number\n ci95: [number, number]\n cohensD: number\n })\n | (WelchTestResultBase & {\n status: 'insufficient-sample' | 'zero-variance'\n standardError: number\n t: number\n df: number\n p: number\n ci95: null\n cohensD: null\n })\n\n/**\n * Compare candidate samples against baseline per metric. Verdict logic:\n * - unstable: IQR/|mean| > threshold on either set — not enough signal\n * - improved: meaningful effect in the \"better\" direction AND p < alpha\n * - regressed: meaningful effect in the \"worse\" direction AND p < alpha\n * - stable: otherwise (no significant change)\n */\nexport function compareToBaseline(\n samples: MetricSamples[],\n options: BaselineOptions = {},\n): BaselineReport {\n const effectThreshold = options.effectThreshold ?? 0.5\n const alpha = options.alpha ?? 0.05\n const cvThreshold = options.unstableCvThreshold ?? 0.3\n\n const metrics: MetricVerdict[] = samples.map((s) => {\n const comparison = welchsTTest(s.baseline, s.candidate)\n if (comparison.status === 'insufficient-sample') {\n throw new Error(`compareToBaseline: need ≥2 samples per side for \"${s.metric}\"`)\n }\n const { meanA: bMean, meanB: cMean, delta, cohensD: d, t, df, p } = comparison\n // Stability is per-side: a comparison is trustworthy only when BOTH\n // samples are internally consistent. Combining the sides would flag\n // large-but-real deltas as \"unstable\" which is exactly what we want\n // to detect.\n const baselineIqr = iqr(s.baseline)\n const candidateIqr = iqr(s.candidate)\n const baselineStable = baselineIqr / Math.max(Math.abs(bMean), 1e-9) <= cvThreshold\n const candidateStable = candidateIqr / Math.max(Math.abs(cMean), 1e-9) <= cvThreshold\n const stable = baselineStable && candidateStable\n const reportedIqr = Math.max(baselineIqr, candidateIqr)\n\n // Both sides are guaranteed ≥ 2 samples above, so a null d means zero\n // pooled spread across a real mean gap: an unbounded standardized effect,\n // which clears any finite threshold.\n const effectClears = d === null || Math.abs(d) >= effectThreshold\n\n let verdict: MetricVerdict['verdict']\n if (!stable) {\n verdict = 'unstable'\n } else if (comparison.status === 'ok' && p < alpha && effectClears) {\n const candidateIsBetter = s.higherIsBetter ? delta > 0 : delta < 0\n verdict = candidateIsBetter ? 'improved' : 'regressed'\n } else {\n verdict = 'stable'\n }\n\n return {\n metric: s.metric,\n baselineMean: bMean,\n candidateMean: cMean,\n delta,\n cohensD: d,\n welchT: t,\n welchDf: df,\n welchP: p,\n stable,\n iqr: reportedIqr,\n verdict,\n }\n })\n\n return {\n metrics,\n hasRegression: metrics.some((m) => m.verdict === 'regressed'),\n hasUnstable: metrics.some((m) => m.verdict === 'unstable'),\n }\n}\n\nfunction mean(xs: number[]): number {\n return xs.reduce((a, b) => a + b, 0) / xs.length\n}\n\n/** Inter-quartile range; 0 when the sample has no spread. */\nexport function iqr(xs: number[]): number {\n if (xs.length === 0) return 0\n const sorted = [...xs].sort((a, b) => a - b)\n const q = (p: number) => {\n const idx = p * (sorted.length - 1)\n const lo = Math.floor(idx)\n const hi = Math.ceil(idx)\n return sorted[lo]! + (sorted[hi]! - sorted[lo]!) * (idx - lo)\n }\n return q(0.75) - q(0.25)\n}\n\n/**\n * Welch's t-test and 95% interval for two independent samples.\n *\n * Uses the Student-t distribution at every finite degree of freedom. Fewer\n * than two observations per side and zero observed standard error cannot\n * define a t reference distribution; those cases return an explicit status,\n * `NaN` inferential fields, and no interval rather than fabricated certainty.\n */\nexport function welchsTTest(a: number[], b: number[]): WelchTestResult {\n assertFiniteSample('a', a)\n assertFiniteSample('b', b)\n const mA = mean(a)\n const mB = mean(b)\n const delta = mB - mA\n if (a.length < 2 || b.length < 2) {\n return {\n status: 'insufficient-sample',\n meanA: mA,\n meanB: mB,\n delta,\n standardError: Number.NaN,\n t: Number.NaN,\n df: Number.NaN,\n p: Number.NaN,\n ci95: null,\n cohensD: null,\n }\n }\n\n const vA = variance(a, mA)\n const vB = variance(b, mB)\n const seSquared = vA / a.length + vB / b.length\n const d = cohensD(a, b)\n if (seSquared === 0) {\n return {\n status: 'zero-variance',\n meanA: mA,\n meanB: mB,\n delta,\n standardError: 0,\n t: delta === 0 ? 0 : Math.sign(delta) * Number.POSITIVE_INFINITY,\n df: Number.NaN,\n p: Number.NaN,\n ci95: null,\n cohensD: null,\n }\n }\n\n const standardError = Math.sqrt(seSquared)\n const t = delta / standardError\n const df =\n (seSquared * seSquared) /\n ((vA / a.length) ** 2 / (a.length - 1) + (vB / b.length) ** 2 / (b.length - 1))\n const p = 2 * (1 - studentTCdf(Math.abs(t), df))\n if (d === null) {\n throw new Error('welchsTTest: non-zero standard error produced no pooled effect size')\n }\n const halfWidth = studentTQuantile(0.975, df) * standardError\n return {\n status: 'ok',\n meanA: mA,\n meanB: mB,\n delta,\n standardError,\n t,\n df,\n p,\n ci95: [delta - halfWidth, delta + halfWidth],\n cohensD: d,\n }\n}\n\nfunction variance(xs: number[], m: number): number {\n return xs.reduce((acc, x) => acc + (x - m) ** 2, 0) / (xs.length - 1)\n}\n\nfunction assertFiniteSample(name: string, sample: readonly number[]): void {\n const invalid = sample.find((value) => !Number.isFinite(value))\n if (invalid !== undefined) {\n throw new RangeError(\n `welchsTTest: sample ${name} must contain only finite values, got ${invalid}`,\n )\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AAwGA,SAAgB,kBACd,SACA,UAA2B,CAAC,GACZ;CAChB,MAAM,kBAAkB,QAAQ,mBAAmB;CACnD,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,cAAc,QAAQ,uBAAuB;CAEnD,MAAM,UAA2B,QAAQ,KAAK,MAAM;EAClD,MAAM,aAAa,YAAY,EAAE,UAAU,EAAE,SAAS;EACtD,IAAI,WAAW,WAAW,uBACxB,MAAM,IAAI,MAAM,oDAAoD,EAAE,OAAO,EAAE;EAEjF,MAAM,EAAE,OAAO,OAAO,OAAO,OAAO,OAAO,SAAS,GAAG,GAAG,IAAI,MAAM;EAKpE,MAAM,cAAc,IAAI,EAAE,QAAQ;EAClC,MAAM,eAAe,IAAI,EAAE,SAAS;EACpC,MAAM,iBAAiB,cAAc,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,KAAK;EACxE,MAAM,kBAAkB,eAAe,KAAK,IAAI,KAAK,IAAI,KAAK,GAAG,IAAI,KAAK;EAC1E,MAAM,SAAS,kBAAkB;EACjC,MAAM,cAAc,KAAK,IAAI,aAAa,YAAY;EAKtD,MAAM,eAAe,MAAM,QAAQ,KAAK,IAAI,CAAC,KAAK;EAElD,IAAI;EACJ,IAAI,CAAC,QACH,UAAU;OACL,IAAI,WAAW,WAAW,QAAQ,IAAI,SAAS,cAEpD,WAD0B,EAAE,iBAAiB,QAAQ,IAAI,QAAQ,KACnC,aAAa;OAE3C,UAAU;EAGZ,OAAO;GACL,QAAQ,EAAE;GACV,cAAc;GACd,eAAe;GACf;GACA,SAAS;GACT,QAAQ;GACR,SAAS;GACT,QAAQ;GACR;GACA,KAAK;GACL;EACF;CACF,CAAC;CAED,OAAO;EACL;EACA,eAAe,QAAQ,MAAM,MAAM,EAAE,YAAY,WAAW;EAC5D,aAAa,QAAQ,MAAM,MAAM,EAAE,YAAY,UAAU;CAC3D;AACF;AAEA,SAAS,KAAK,IAAsB;CAClC,OAAO,GAAG,QAAQ,GAAG,MAAM,IAAI,GAAG,CAAC,IAAI,GAAG;AAC5C;;AAGA,SAAgB,IAAI,IAAsB;CACxC,IAAI,GAAG,WAAW,GAAG,OAAO;CAC5B,MAAM,SAAS,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAC3C,MAAM,KAAK,MAAc;EACvB,MAAM,MAAM,KAAK,OAAO,SAAS;EACjC,MAAM,KAAK,KAAK,MAAM,GAAG;EACzB,MAAM,KAAK,KAAK,KAAK,GAAG;EACxB,OAAO,OAAO,OAAQ,OAAO,MAAO,OAAO,QAAS,MAAM;CAC5D;CACA,OAAO,EAAE,GAAI,IAAI,EAAE,GAAI;AACzB;;;;;;;;;AAUA,SAAgB,YAAY,GAAa,GAA8B;CACrE,mBAAmB,KAAK,CAAC;CACzB,mBAAmB,KAAK,CAAC;CACzB,MAAM,KAAK,KAAK,CAAC;CACjB,MAAM,KAAK,KAAK,CAAC;CACjB,MAAM,QAAQ,KAAK;CACnB,IAAI,EAAE,SAAS,KAAK,EAAE,SAAS,GAC7B,OAAO;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP;EACA,eAAe;EACf,GAAG;EACH,IAAI;EACJ,GAAG;EACH,MAAM;EACN,SAAS;CACX;CAGF,MAAM,KAAK,SAAS,GAAG,EAAE;CACzB,MAAM,KAAK,SAAS,GAAG,EAAE;CACzB,MAAM,YAAY,KAAK,EAAE,SAAS,KAAK,EAAE;CACzC,MAAM,IAAI,QAAQ,GAAG,CAAC;CACtB,IAAI,cAAc,GAChB,OAAO;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP;EACA,eAAe;EACf,GAAG,UAAU,IAAI,IAAI,KAAK,KAAK,KAAK,IAAI,OAAO;EAC/C,IAAI;EACJ,GAAG;EACH,MAAM;EACN,SAAS;CACX;CAGF,MAAM,gBAAgB,KAAK,KAAK,SAAS;CACzC,MAAM,IAAI,QAAQ;CAClB,MAAM,KACH,YAAY,cACX,KAAK,EAAE,WAAW,KAAK,EAAE,SAAS,MAAM,KAAK,EAAE,WAAW,KAAK,EAAE,SAAS;CAC9E,MAAM,IAAI,KAAK,IAAI,YAAY,KAAK,IAAI,CAAC,GAAG,EAAE;CAC9C,IAAI,MAAM,MACR,MAAM,IAAI,MAAM,qEAAqE;CAEvF,MAAM,YAAY,iBAAiB,MAAO,EAAE,IAAI;CAChD,OAAO;EACL,QAAQ;EACR,OAAO;EACP,OAAO;EACP;EACA;EACA;EACA;EACA;EACA,MAAM,CAAC,QAAQ,WAAW,QAAQ,SAAS;EAC3C,SAAS;CACX;AACF;AAEA,SAAS,SAAS,IAAc,GAAmB;CACjD,OAAO,GAAG,QAAQ,KAAK,MAAM,OAAO,IAAI,MAAM,GAAG,CAAC,KAAK,GAAG,SAAS;AACrE;AAEA,SAAS,mBAAmB,MAAc,QAAiC;CACzE,MAAM,UAAU,OAAO,MAAM,UAAU,CAAC,OAAO,SAAS,KAAK,CAAC;CAC9D,IAAI,YAAY,KAAA,GACd,MAAM,IAAI,WACR,uBAAuB,KAAK,wCAAwC,SACtE;AAEJ"}
1
+ {"version":3,"file":"baseline-CVKsfWTg.js","names":[],"sources":["../src/baseline.ts"],"sourcesContent":["/**\n * Baseline regression detection.\n *\n * Lifted from ADC baseline.ts. Every promotion-blocking signal boils down\n * to: \"is this run measurably worse than baseline?\" — with enough\n * statistical rigor to distinguish noise from drift.\n *\n * Uses:\n * - Welch's t-test (unequal variance) for per-metric mean comparison\n * - Cohen's d for effect size magnitude\n * - IQR for stability flag (unstable samples can't be trusted for comparisons)\n *\n * Returns a structured verdict: improved | regressed | stable | unstable.\n */\n\nimport { studentTCdf, studentTQuantile } from './math/student-t'\nimport { cohensD } from './statistics'\n\nexport interface MetricSamples {\n /** Stable metric key (e.g. \"overallScore\", \"firstTokenMs\"). */\n metric: string\n /** Whether higher values are better. */\n higherIsBetter: boolean\n baseline: number[]\n candidate: number[]\n}\n\nexport interface MetricVerdict {\n metric: string\n baselineMean: number\n candidateMean: number\n delta: number\n /** Null when both samples are constant with unequal means — the\n * standardized effect is unbounded there, not zero. */\n cohensD: number | null\n welchT: number\n welchDf: number\n welchP: number\n stable: boolean\n /** IQR of the combined samples — used as a rough stability indicator. */\n iqr: number\n verdict: 'improved' | 'regressed' | 'stable' | 'unstable'\n}\n\nexport interface BaselineReport {\n metrics: MetricVerdict[]\n /** True if any critical metric regressed. */\n hasRegression: boolean\n /** True if any metric is unstable (too noisy to judge). */\n hasUnstable: boolean\n}\n\nexport interface BaselineOptions {\n /** Effect size threshold for meaningful delta (default 0.5 — medium effect). */\n effectThreshold?: number\n /** p-value threshold for statistical significance (default 0.05). */\n alpha?: number\n /** IQR/mean ratio above which samples are flagged unstable (default 0.30). */\n unstableCvThreshold?: number\n}\n\nexport type WelchTestStatus = 'ok' | 'insufficient-sample' | 'zero-variance'\n\n/**\n * Full unequal-variance comparison for two independent samples.\n *\n * `meanB`, `delta`, `t`, and `cohensD` are oriented as `b - a`. Inferential\n * fields are `NaN` and `ci95` is null when the sample count or observed\n * variance cannot define a Student-t result. `status` makes that state\n * explicit so callers cannot mistake it for evidence of no difference.\n */\ninterface WelchTestResultBase {\n meanA: number\n meanB: number\n delta: number\n}\n\nexport type WelchTestResult =\n | (WelchTestResultBase & {\n status: 'ok'\n standardError: number\n t: number\n df: number\n p: number\n ci95: [number, number]\n cohensD: number\n })\n | (WelchTestResultBase & {\n status: 'insufficient-sample' | 'zero-variance'\n standardError: number\n t: number\n df: number\n p: number\n ci95: null\n cohensD: null\n })\n\n/**\n * Compare candidate samples against baseline per metric. Verdict logic:\n * - unstable: IQR/|mean| > threshold on either set — not enough signal\n * - improved: meaningful effect in the \"better\" direction AND p < alpha\n * - regressed: meaningful effect in the \"worse\" direction AND p < alpha\n * - stable: otherwise (no significant change)\n */\nexport function compareToBaseline(\n samples: MetricSamples[],\n options: BaselineOptions = {},\n): BaselineReport {\n const effectThreshold = options.effectThreshold ?? 0.5\n const alpha = options.alpha ?? 0.05\n const cvThreshold = options.unstableCvThreshold ?? 0.3\n\n const metrics: MetricVerdict[] = samples.map((s) => {\n const comparison = welchsTTest(s.baseline, s.candidate)\n if (comparison.status === 'insufficient-sample') {\n throw new Error(`compareToBaseline: need ≥2 samples per side for \"${s.metric}\"`)\n }\n const { meanA: bMean, meanB: cMean, delta, cohensD: d, t, df, p } = comparison\n // Stability is per-side: a comparison is trustworthy only when BOTH\n // samples are internally consistent. Combining the sides would flag\n // large-but-real deltas as \"unstable\" which is exactly what we want\n // to detect.\n const baselineIqr = iqr(s.baseline)\n const candidateIqr = iqr(s.candidate)\n const baselineStable = baselineIqr / Math.max(Math.abs(bMean), 1e-9) <= cvThreshold\n const candidateStable = candidateIqr / Math.max(Math.abs(cMean), 1e-9) <= cvThreshold\n const stable = baselineStable && candidateStable\n const reportedIqr = Math.max(baselineIqr, candidateIqr)\n\n // Both sides are guaranteed ≥ 2 samples above, so a null d means zero\n // pooled spread across a real mean gap: an unbounded standardized effect,\n // which clears any finite threshold.\n const effectClears = d === null || Math.abs(d) >= effectThreshold\n\n let verdict: MetricVerdict['verdict']\n if (!stable) {\n verdict = 'unstable'\n } else if (comparison.status === 'ok' && p < alpha && effectClears) {\n const candidateIsBetter = s.higherIsBetter ? delta > 0 : delta < 0\n verdict = candidateIsBetter ? 'improved' : 'regressed'\n } else {\n verdict = 'stable'\n }\n\n return {\n metric: s.metric,\n baselineMean: bMean,\n candidateMean: cMean,\n delta,\n cohensD: d,\n welchT: t,\n welchDf: df,\n welchP: p,\n stable,\n iqr: reportedIqr,\n verdict,\n }\n })\n\n return {\n metrics,\n hasRegression: metrics.some((m) => m.verdict === 'regressed'),\n hasUnstable: metrics.some((m) => m.verdict === 'unstable'),\n }\n}\n\nfunction mean(xs: number[]): number {\n return xs.reduce((a, b) => a + b, 0) / xs.length\n}\n\n/** Inter-quartile range; 0 when the sample has no spread. */\nexport function iqr(xs: number[]): number {\n if (xs.length === 0) return 0\n const sorted = [...xs].sort((a, b) => a - b)\n const q = (p: number) => {\n const idx = p * (sorted.length - 1)\n const lo = Math.floor(idx)\n const hi = Math.ceil(idx)\n return sorted[lo]! + (sorted[hi]! - sorted[lo]!) * (idx - lo)\n }\n return q(0.75) - q(0.25)\n}\n\n/**\n * Welch's t-test and 95% interval for two independent samples.\n *\n * Uses the Student-t distribution at every finite degree of freedom. Fewer\n * than two observations per side and zero observed standard error cannot\n * define a t reference distribution; those cases return an explicit status,\n * `NaN` inferential fields, and no interval rather than fabricated certainty.\n */\nexport function welchsTTest(a: number[], b: number[]): WelchTestResult {\n assertFiniteSample('a', a)\n assertFiniteSample('b', b)\n const mA = mean(a)\n const mB = mean(b)\n const delta = mB - mA\n if (a.length < 2 || b.length < 2) {\n return {\n status: 'insufficient-sample',\n meanA: mA,\n meanB: mB,\n delta,\n standardError: Number.NaN,\n t: Number.NaN,\n df: Number.NaN,\n p: Number.NaN,\n ci95: null,\n cohensD: null,\n }\n }\n\n const vA = variance(a, mA)\n const vB = variance(b, mB)\n const seSquared = vA / a.length + vB / b.length\n const d = cohensD(a, b)\n if (seSquared === 0) {\n return {\n status: 'zero-variance',\n meanA: mA,\n meanB: mB,\n delta,\n standardError: 0,\n t: delta === 0 ? 0 : Math.sign(delta) * Number.POSITIVE_INFINITY,\n df: Number.NaN,\n p: Number.NaN,\n ci95: null,\n cohensD: null,\n }\n }\n\n const standardError = Math.sqrt(seSquared)\n const t = delta / standardError\n const df =\n (seSquared * seSquared) /\n ((vA / a.length) ** 2 / (a.length - 1) + (vB / b.length) ** 2 / (b.length - 1))\n const p = 2 * (1 - studentTCdf(Math.abs(t), df))\n if (d === null) {\n throw new Error('welchsTTest: non-zero standard error produced no pooled effect size')\n }\n const halfWidth = studentTQuantile(0.975, df) * standardError\n return {\n status: 'ok',\n meanA: mA,\n meanB: mB,\n delta,\n standardError,\n t,\n df,\n p,\n ci95: [delta - halfWidth, delta + halfWidth],\n cohensD: d,\n }\n}\n\nfunction variance(xs: number[], m: number): number {\n return xs.reduce((acc, x) => acc + (x - m) ** 2, 0) / (xs.length - 1)\n}\n\nfunction assertFiniteSample(name: string, sample: readonly number[]): void {\n const invalid = sample.find((value) => !Number.isFinite(value))\n if (invalid !== undefined) {\n throw new RangeError(\n `welchsTTest: sample ${name} must contain only finite values, got ${invalid}`,\n )\n }\n}\n"],"mappings":"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"}
@@ -56,11 +56,12 @@ function harmonicMeanScore(a, b) {
56
56
  function matchFindingsToIssues(issues, findings) {
57
57
  if (issues.length === 0) return /* @__PURE__ */ new Map();
58
58
  const cardinalityWeight = issues.length + 1;
59
- const assignment = linearSumAssignment(issues.map((issue) => [...findings.map((finding) => {
59
+ const scores = issues.map((issue) => [...findings.map((finding) => {
60
60
  if (!findingMatchesIssue(finding, issue)) return -1;
61
61
  const criticalHit = (issue.criticalEvidence?.length ?? 0) > 0 && matchesEvidence(finding.evidence_refs, issue.criticalEvidence ?? [], "any");
62
62
  return cardinalityWeight + Number(criticalHit);
63
- }), ...Array.from({ length: issues.length }, () => 0)]), { maximaze: true }).rowAssignments;
63
+ }), ...Array.from({ length: issues.length }, () => 0)]);
64
+ const assignment = linearSumAssignment(scores, { maximaze: true }).rowAssignments;
64
65
  const matches = /* @__PURE__ */ new Map();
65
66
  for (const [issueIndex, column] of assignment.entries()) {
66
67
  if (column < 0 || column >= findings.length) continue;
@@ -558,4 +559,4 @@ function mergeRegistryUsage(result) {
558
559
  //#endregion
559
560
  export { scoreAnalystFindings as a, summarizeAnalystBenchmarkRunner as i, runAnalystBenchmark as n, traceStoreEvidenceResolver as r, registryBenchmarkRunner as t };
560
561
 
561
- //# sourceMappingURL=benchmark-BeQJyrRC.js.map
562
+ //# sourceMappingURL=benchmark-BywPeAUo.js.map