@tangle-network/agent-eval 0.217.1 → 0.219.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 (258) hide show
  1. package/CHANGELOG.md +57 -1
  2. package/dist/adapters/http.d.ts +3 -3
  3. package/dist/adapters/http.d.ts.map +1 -1
  4. package/dist/adapters/http.js +1 -0
  5. package/dist/adapters/http.js.map +1 -1
  6. package/dist/{agent-profile-DTt8uGP9.d.ts → agent-profile-D01Io2Xw.d.ts} +4 -4
  7. package/dist/{agent-profile-DTt8uGP9.d.ts.map → agent-profile-D01Io2Xw.d.ts.map} +1 -1
  8. package/dist/analyst/index.d.ts +13 -13
  9. package/dist/analyst/index.d.ts.map +1 -1
  10. package/dist/analyst/index.js +10 -10
  11. package/dist/analyst/index.js.map +1 -1
  12. package/dist/{analyze-runs-DyeNcenW.d.ts → analyze-runs-BWAHBDAw.d.ts} +4 -4
  13. package/dist/{analyze-runs-DyeNcenW.d.ts.map → analyze-runs-BWAHBDAw.d.ts.map} +1 -1
  14. package/dist/{benchmark-command-xQK8nOI6.js → benchmark-command-BzmMTnvC.js} +664 -565
  15. package/dist/benchmark-command-BzmMTnvC.js.map +1 -0
  16. package/dist/benchmarks/index.d.ts +5 -5
  17. package/dist/benchmarks/index.js +3 -3
  18. package/dist/benchmarks/index.js.map +1 -1
  19. package/dist/campaign/index.d.ts +8 -8
  20. package/dist/campaign/index.js +11 -11
  21. package/dist/{campaign-CJGkxAQ6.js → campaign-BVPC6ODQ.js} +452 -282
  22. package/dist/campaign-BVPC6ODQ.js.map +1 -0
  23. package/dist/{campaign-evidence-CQYw8_0H.js → campaign-evidence-DunzcJGv.js} +2 -2
  24. package/dist/campaign-evidence-DunzcJGv.js.map +1 -0
  25. package/dist/{chat-client-ldqxYYTN.js → chat-client-CMQ8_h4J.js} +2 -2
  26. package/dist/{chat-client-ldqxYYTN.js.map → chat-client-CMQ8_h4J.js.map} +1 -1
  27. package/dist/{chat-json-call-DavzexSp.js → chat-json-call-BrZKn0L3.js} +2 -2
  28. package/dist/{chat-json-call-DavzexSp.js.map → chat-json-call-BrZKn0L3.js.map} +1 -1
  29. package/dist/cli.js +10 -10
  30. package/dist/{completion-verifier-B_jw6C9p.js → completion-verifier-Cigap8CG.js} +5 -5
  31. package/dist/{completion-verifier-B_jw6C9p.js.map → completion-verifier-Cigap8CG.js.map} +1 -1
  32. package/dist/contract/index.d.ts +11 -11
  33. package/dist/contract/index.js +91 -11
  34. package/dist/contract/index.js.map +1 -1
  35. package/dist/{cost-ledger-D0352Z4d.js → cost-ledger-BGZB6yY2.js} +75 -2
  36. package/dist/cost-ledger-BGZB6yY2.js.map +1 -0
  37. package/dist/{cost-ledger-Jk62OBfF.d.ts → cost-ledger-C1W3ICmP.d.ts} +26 -2
  38. package/dist/cost-ledger-C1W3ICmP.d.ts.map +1 -0
  39. package/dist/{default-registry-ClYjq3QE.js → default-registry-BrKmT01n.js} +310 -278
  40. package/dist/{default-registry-ClYjq3QE.js.map → default-registry-BrKmT01n.js.map} +1 -1
  41. package/dist/{default-registry-DXgLmoDI.d.ts → default-registry-Cwg_QN3d.d.ts} +6 -6
  42. package/dist/{default-registry-DXgLmoDI.d.ts.map → default-registry-Cwg_QN3d.d.ts.map} +1 -1
  43. package/dist/{define-agent-eval-BIkH_gAR.d.ts → define-agent-eval-C2nFhHPu.d.ts} +8 -8
  44. package/dist/{define-agent-eval-BIkH_gAR.d.ts.map → define-agent-eval-C2nFhHPu.d.ts.map} +1 -1
  45. package/dist/{define-agent-eval-BDxNlP3D.js → define-agent-eval-UQY3VbJ2.js} +165 -105
  46. package/dist/define-agent-eval-UQY3VbJ2.js.map +1 -0
  47. package/dist/diagnosis/index.d.ts +2 -2
  48. package/dist/{dspy-rlm-engine-DhdCr1Dc.js → dspy-rlm-engine-Bn_Mt_LW.js} +3 -3
  49. package/dist/{dspy-rlm-engine-DhdCr1Dc.js.map → dspy-rlm-engine-Bn_Mt_LW.js.map} +1 -1
  50. package/dist/{engine-BvZ0R-36.d.ts → engine-BpGGw6cd.d.ts} +3 -3
  51. package/dist/{engine-BvZ0R-36.d.ts.map → engine-BpGGw6cd.d.ts.map} +1 -1
  52. package/dist/eval-campaign-BUMIxKTE.js +393 -0
  53. package/dist/eval-campaign-BUMIxKTE.js.map +1 -0
  54. package/dist/{evaluation-jTjC_QQD.d.ts → evaluation-CBdQt_kr.d.ts} +4 -4
  55. package/dist/{evaluation-jTjC_QQD.d.ts.map → evaluation-CBdQt_kr.d.ts.map} +1 -1
  56. package/dist/evaluation.d.ts +1 -1
  57. package/dist/evaluation.js +1 -1
  58. package/dist/{exact-types-CflbJ9f3.d.ts → exact-types-BYFmYq44.d.ts} +2 -2
  59. package/dist/{exact-types-CflbJ9f3.d.ts.map → exact-types-BYFmYq44.d.ts.map} +1 -1
  60. package/dist/experiment/index.d.ts +24 -1
  61. package/dist/experiment/index.d.ts.map +1 -1
  62. package/dist/experiment/index.js +110 -71
  63. package/dist/experiment/index.js.map +1 -1
  64. package/dist/{exporters-Cl6IeNAu.js → exporters-DWvM16LE.js} +2 -2
  65. package/dist/{exporters-Cl6IeNAu.js.map → exporters-DWvM16LE.js.map} +1 -1
  66. package/dist/{external-optimizer-process-KVFGvYX1.js → external-optimizer-process-wOLfzsCC.js} +2 -2
  67. package/dist/{external-optimizer-process-KVFGvYX1.js.map → external-optimizer-process-wOLfzsCC.js.map} +1 -1
  68. package/dist/{external-optimizer-subprocess-XIHBOSKt.js → external-optimizer-subprocess-D670Oxf2.js} +4 -4
  69. package/dist/{external-optimizer-subprocess-XIHBOSKt.js.map → external-optimizer-subprocess-D670Oxf2.js.map} +1 -1
  70. package/dist/{feedback-trajectory-BWmhRBGu.d.ts → feedback-trajectory-DhB9WzYg.d.ts} +3 -3
  71. package/dist/{feedback-trajectory-BWmhRBGu.d.ts.map → feedback-trajectory-DhB9WzYg.d.ts.map} +1 -1
  72. package/dist/{findings-store-BMVQtEsm.js → findings-store-CvNo712L.js} +6 -6
  73. package/dist/{findings-store-BMVQtEsm.js.map → findings-store-CvNo712L.js.map} +1 -1
  74. package/dist/fuzz.d.ts +1 -1
  75. package/dist/fuzz.js +1 -1
  76. package/dist/{hf-dataset-D9f7-oX7.js → hf-dataset-CKxIJQba.js} +3 -3
  77. package/dist/{hf-dataset-D9f7-oX7.js.map → hf-dataset-CKxIJQba.js.map} +1 -1
  78. package/dist/hosted/index.js +1 -1
  79. package/dist/{index-CX-GAUpw.d.ts → index-CHxwfW5g.d.ts} +2 -2
  80. package/dist/{index-CX-GAUpw.d.ts.map → index-CHxwfW5g.d.ts.map} +1 -1
  81. package/dist/{index-BLGqZ81N.d.ts → index-CVFStrQD.d.ts} +8 -8
  82. package/dist/{index-BLGqZ81N.d.ts.map → index-CVFStrQD.d.ts.map} +1 -1
  83. package/dist/{index-9jJ2fKSX.d.ts → index-CalAsXMR.d.ts} +7 -5
  84. package/dist/index-CalAsXMR.d.ts.map +1 -0
  85. package/dist/{index-CiRwLv2R.d.ts → index-D7fmC-1K.d.ts} +3 -4
  86. package/dist/index-D7fmC-1K.d.ts.map +1 -0
  87. package/dist/{index-BiT6RtNH.d.ts → index-DAXfpVf7.d.ts} +10 -10
  88. package/dist/{index-BiT6RtNH.d.ts.map → index-DAXfpVf7.d.ts.map} +1 -1
  89. package/dist/{index-ZuWx52WG.d.ts → index-Q6DvdcwI.d.ts} +67 -24
  90. package/dist/index-Q6DvdcwI.d.ts.map +1 -0
  91. package/dist/index.d.ts +26 -26
  92. package/dist/index.d.ts.map +1 -1
  93. package/dist/index.js +615 -692
  94. package/dist/index.js.map +1 -1
  95. package/dist/{integrity-DOd9Q86O.js → integrity-C8oUeB0g.js} +322 -197
  96. package/dist/integrity-C8oUeB0g.js.map +1 -0
  97. package/dist/{integrity-_qgZLA5p.d.ts → integrity-CCwvezWS.d.ts} +2 -2
  98. package/dist/{integrity-_qgZLA5p.d.ts.map → integrity-CCwvezWS.d.ts.map} +1 -1
  99. package/dist/{journal-CR4QikYp.js → journal-C7om8Gum.js} +48 -38
  100. package/dist/journal-C7om8Gum.js.map +1 -0
  101. package/dist/judge-gate-Co5tuh57.js.map +1 -1
  102. package/dist/{judge-gate-DsL0yFcb.d.ts → judge-gate-Dc_mXQsL.d.ts} +2 -2
  103. package/dist/judge-gate-Dc_mXQsL.d.ts.map +1 -0
  104. package/dist/{kind-factory-Gy4fXixd.js → kind-factory-BhxbtUsQ.js} +2 -2
  105. package/dist/{kind-factory-Gy4fXixd.js.map → kind-factory-BhxbtUsQ.js.map} +1 -1
  106. package/dist/ledger-core/index.d.ts +1 -1
  107. package/dist/ledger-core/index.js +2 -2
  108. package/dist/ledger-core/lock-holder.d.ts +2 -0
  109. package/dist/ledger-core/lock-holder.js +181 -0
  110. package/dist/ledger-core/lock-holder.js.map +1 -0
  111. package/dist/{ledger-core-DrJhbms9.js → ledger-core-CAxUdDk0.js} +2 -2
  112. package/dist/{ledger-core-DrJhbms9.js.map → ledger-core-CAxUdDk0.js.map} +1 -1
  113. package/dist/{llm-client-BshCREn6.js → llm-client-DXuOTseU.js} +239 -249
  114. package/dist/llm-client-DXuOTseU.js.map +1 -0
  115. package/dist/{llm-judge-C8jMTNNG.js → llm-judge-Cc8vDJh_.js} +648 -459
  116. package/dist/llm-judge-Cc8vDJh_.js.map +1 -0
  117. package/dist/lock-holder-DaJbfpci.d.ts +86 -0
  118. package/dist/lock-holder-DaJbfpci.d.ts.map +1 -0
  119. package/dist/matrix/index.d.ts +2 -2
  120. package/dist/{matrix-DHe-dwVJ.d.ts → matrix-Cf-AGxGh.d.ts} +4 -4
  121. package/dist/{matrix-DHe-dwVJ.d.ts.map → matrix-Cf-AGxGh.d.ts.map} +1 -1
  122. package/dist/meta-eval/index.d.ts +2 -2
  123. package/dist/{mint-zszyYlpA.js → mint-CbdwjTPW.js} +2 -2
  124. package/dist/{mint-zszyYlpA.js.map → mint-CbdwjTPW.js.map} +1 -1
  125. package/dist/{mint-BRZXuwRi.d.ts → mint-CkzpVPdz.d.ts} +3 -3
  126. package/dist/{mint-BRZXuwRi.d.ts.map → mint-CkzpVPdz.d.ts.map} +1 -1
  127. package/dist/multishot/golden/index.d.ts +1 -1
  128. package/dist/multishot/index.d.ts +2 -2
  129. package/dist/multishot/index.js.map +1 -1
  130. package/dist/openapi.json +1 -1
  131. package/dist/{pareto-BzsxidFD.js → pareto-BiBnQXAE.js} +306 -212
  132. package/dist/pareto-BiBnQXAE.js.map +1 -0
  133. package/dist/pipelines/index.d.ts.map +1 -1
  134. package/dist/pipelines/index.js.map +1 -1
  135. package/dist/{prime-protocol-BWG2DQIk.d.ts → prime-protocol-CckHBxGu.d.ts} +3 -3
  136. package/dist/{prime-protocol-BWG2DQIk.d.ts.map → prime-protocol-CckHBxGu.d.ts.map} +1 -1
  137. package/dist/{promotion-gate-DaY95yU6.d.ts → promotion-gate-CkVZHagC.d.ts} +3 -3
  138. package/dist/{promotion-gate-DaY95yU6.d.ts.map → promotion-gate-CkVZHagC.d.ts.map} +1 -1
  139. package/dist/{proposal-findings-BbWidvUk.js → proposal-findings-E1aSC_cU.js} +2 -2
  140. package/dist/{proposal-findings-BbWidvUk.js.map → proposal-findings-E1aSC_cU.js.map} +1 -1
  141. package/dist/{registry-ssNoHzwD.d.ts → registry-B_yG-1jh.d.ts} +5 -5
  142. package/dist/{registry-ssNoHzwD.d.ts.map → registry-B_yG-1jh.d.ts.map} +1 -1
  143. package/dist/{render-C-w2EZ6p.js → render-Cs34y0iX.js} +404 -208
  144. package/dist/render-Cs34y0iX.js.map +1 -0
  145. package/dist/reporting.d.ts +3 -3
  146. package/dist/{researcher-0ABVIdGa.d.ts → researcher-H0p44OYC.d.ts} +5 -5
  147. package/dist/{researcher-0ABVIdGa.d.ts.map → researcher-H0p44OYC.d.ts.map} +1 -1
  148. package/dist/{reward-DrZ71vnf.d.ts → reward-BeUgVVC_.d.ts} +2 -2
  149. package/dist/{reward-DrZ71vnf.d.ts.map → reward-BeUgVVC_.d.ts.map} +1 -1
  150. package/dist/{reward-hacking-E52wq86n.d.ts → reward-hacking-WTxUbtbb.d.ts} +2 -2
  151. package/dist/{reward-hacking-E52wq86n.d.ts.map → reward-hacking-WTxUbtbb.d.ts.map} +1 -1
  152. package/dist/rl.d.ts +8 -8
  153. package/dist/rl.js +4 -4
  154. package/dist/rollout/index.d.ts +3 -3
  155. package/dist/rollout/index.js +5 -5
  156. package/dist/{rollout-D6C50mfA.js → rollout-Dd1ucT_M.js} +5 -5
  157. package/dist/{rollout-D6C50mfA.js.map → rollout-Dd1ucT_M.js.map} +1 -1
  158. package/dist/{rubric-predictive-validity-DiwT8PbL.d.ts → rubric-predictive-validity-noY7uE03.d.ts} +2 -2
  159. package/dist/{rubric-predictive-validity-DiwT8PbL.d.ts.map → rubric-predictive-validity-noY7uE03.d.ts.map} +1 -1
  160. package/dist/{run-record-CaxbLq6Y.d.ts → run-record-C3awiXPf.d.ts} +2 -2
  161. package/dist/{run-record-CaxbLq6Y.d.ts.map → run-record-C3awiXPf.d.ts.map} +1 -1
  162. package/dist/schema-C-ziyJD7.d.ts.map +1 -1
  163. package/dist/{schema-DxcKTzbi.js → schema-CvhfLjtZ.js} +77 -47
  164. package/dist/schema-CvhfLjtZ.js.map +1 -0
  165. package/dist/search/index.d.ts +2 -2
  166. package/dist/search/index.d.ts.map +1 -1
  167. package/dist/search/index.js +2 -2
  168. package/dist/{search-kernel-DxqjBxA-.d.ts → search-kernel-Dloxd6gs.d.ts} +5 -5
  169. package/dist/{search-kernel-DxqjBxA-.d.ts.map → search-kernel-Dloxd6gs.d.ts.map} +1 -1
  170. package/dist/{search-ledger-TUvB5gWH.js → search-ledger-CZRqtFbW.js} +252 -245
  171. package/dist/search-ledger-CZRqtFbW.js.map +1 -0
  172. package/dist/{search-ledger-file-UKCO2r8a.js → search-ledger-file-Bd5Tioq6.js} +3 -3
  173. package/dist/{search-ledger-file-UKCO2r8a.js.map → search-ledger-file-Bd5Tioq6.js.map} +1 -1
  174. package/dist/{search-shipper-Cw2z3-td.js → search-shipper-Dn3NLsuH.js} +3 -3
  175. package/dist/{search-shipper-Cw2z3-td.js.map → search-shipper-Dn3NLsuH.js.map} +1 -1
  176. package/dist/{search-summary-DQIvJsSB.js → search-summary-D4bwYnAE.js} +3 -3
  177. package/dist/{search-summary-DQIvJsSB.js.map → search-summary-D4bwYnAE.js.map} +1 -1
  178. package/dist/{server-DoVbqqtJ.js → server-8TS8XCvg.js} +3 -3
  179. package/dist/{server-DoVbqqtJ.js.map → server-8TS8XCvg.js.map} +1 -1
  180. package/dist/{single-run-lock-U58yf8N6.js → single-run-lock-Dekd8_Wv.js} +7 -7
  181. package/dist/single-run-lock-Dekd8_Wv.js.map +1 -0
  182. package/dist/{skillopt-optimization-method-kyL94-ri.js → skillopt-optimization-method-DLKqdhcR.js} +1384 -1059
  183. package/dist/skillopt-optimization-method-DLKqdhcR.js.map +1 -0
  184. package/dist/{statistical-heldout-C9AbgREN.d.ts → statistical-heldout-3z3I-kJN.d.ts} +3 -3
  185. package/dist/{statistical-heldout-C9AbgREN.d.ts.map → statistical-heldout-3z3I-kJN.d.ts.map} +1 -1
  186. package/dist/{store-otlp-BteDAbHK.js → store-otlp-dyWE3wAG.js} +2 -2
  187. package/dist/{store-otlp-BteDAbHK.js.map → store-otlp-dyWE3wAG.js.map} +1 -1
  188. package/dist/{store-tool-spans-D5l_OfTJ.js → store-tool-spans-DIUgZbzu.js} +3 -3
  189. package/dist/{store-tool-spans-D5l_OfTJ.js.map → store-tool-spans-DIUgZbzu.js.map} +1 -1
  190. package/dist/{store-tool-spans-DZS0HWVf.d.ts → store-tool-spans-DPwXHQkH.d.ts} +7 -7
  191. package/dist/{store-tool-spans-DZS0HWVf.d.ts.map → store-tool-spans-DPwXHQkH.d.ts.map} +1 -1
  192. package/dist/{summary-report-DM7tlml1.d.ts → summary-report-C8V49Jt3.d.ts} +18 -3
  193. package/dist/summary-report-C8V49Jt3.d.ts.map +1 -0
  194. package/dist/supervisor-run/index.d.ts.map +1 -1
  195. package/dist/supervisor-run/index.js +3 -3
  196. package/dist/{systemone-Bp_Ac0p1.d.ts → systemone-DVA7t76V.d.ts} +6 -6
  197. package/dist/{systemone-Bp_Ac0p1.d.ts.map → systemone-DVA7t76V.d.ts.map} +1 -1
  198. package/dist/{systemone-review-CwE6JGIp.d.ts → systemone-review-CUBMuTLi.d.ts} +2 -2
  199. package/dist/{systemone-review-CwE6JGIp.d.ts.map → systemone-review-CUBMuTLi.d.ts.map} +1 -1
  200. package/dist/systemone.d.ts +3 -3
  201. package/dist/{terminal-record-78g6E4vh.js → terminal-record-D4Rv-j1W.js} +241 -193
  202. package/dist/{terminal-record-78g6E4vh.js.map → terminal-record-D4Rv-j1W.js.map} +1 -1
  203. package/dist/{tool-groups-DyNz2JZ_.d.ts → tool-groups-LISbBWCg.d.ts} +3 -3
  204. package/dist/tool-groups-LISbBWCg.d.ts.map +1 -0
  205. package/dist/{trace-questions-xxwgsuMi.d.ts → trace-questions-C42fYEE4.d.ts} +4 -4
  206. package/dist/{trace-questions-xxwgsuMi.d.ts.map → trace-questions-C42fYEE4.d.ts.map} +1 -1
  207. package/dist/trace-repair/index.d.ts +3 -3
  208. package/dist/traces.d.ts +6 -6
  209. package/dist/traces.js +3 -3
  210. package/dist/trajectory-replay/index.d.ts +4 -1
  211. package/dist/trajectory-replay/index.d.ts.map +1 -1
  212. package/dist/trajectory-replay/index.js +854 -823
  213. package/dist/trajectory-replay/index.js.map +1 -1
  214. package/dist/{tree-BGRglkKd.js → tree-DSqwJpL9.js} +122 -14
  215. package/dist/tree-DSqwJpL9.js.map +1 -0
  216. package/dist/{types-CW8ewATZ.d.ts → types-8yQzNm8u.d.ts} +4 -4
  217. package/dist/{types-CW8ewATZ.d.ts.map → types-8yQzNm8u.d.ts.map} +1 -1
  218. package/dist/{types-D8GhJoHk.d.ts → types-CHfn1zg5.d.ts} +70 -72
  219. package/dist/types-CHfn1zg5.d.ts.map +1 -0
  220. package/dist/{types-DtMbFw6U.d.ts → types-D8GXK9Z5.d.ts} +3 -3
  221. package/dist/{types-DtMbFw6U.d.ts.map → types-D8GXK9Z5.d.ts.map} +1 -1
  222. package/dist/{types-D-mgRzlh.d.ts → types-eMhdacVp.d.ts} +2 -2
  223. package/dist/{types-D-mgRzlh.d.ts.map → types-eMhdacVp.d.ts.map} +1 -1
  224. package/dist/wire/index.d.ts +3 -3
  225. package/dist/wire/index.js +1 -1
  226. package/docs/campaign-proposers.md +16 -0
  227. package/docs/concepts.md +31 -0
  228. package/docs/design/statistics-decisions.md +1 -1
  229. package/docs/eval-surface-map.md +1 -0
  230. package/docs/profile-json-identity.md +1 -2
  231. package/docs/public-api.md +100 -88
  232. package/package.json +8 -6
  233. package/dist/benchmark-command-xQK8nOI6.js.map +0 -1
  234. package/dist/campaign-CJGkxAQ6.js.map +0 -1
  235. package/dist/campaign-evidence-CQYw8_0H.js.map +0 -1
  236. package/dist/cost-ledger-D0352Z4d.js.map +0 -1
  237. package/dist/cost-ledger-Jk62OBfF.d.ts.map +0 -1
  238. package/dist/define-agent-eval-BDxNlP3D.js.map +0 -1
  239. package/dist/eval-campaign-BLEfBGbA.js +0 -343
  240. package/dist/eval-campaign-BLEfBGbA.js.map +0 -1
  241. package/dist/index-9jJ2fKSX.d.ts.map +0 -1
  242. package/dist/index-CiRwLv2R.d.ts.map +0 -1
  243. package/dist/index-ZuWx52WG.d.ts.map +0 -1
  244. package/dist/integrity-DOd9Q86O.js.map +0 -1
  245. package/dist/journal-CR4QikYp.js.map +0 -1
  246. package/dist/judge-gate-DsL0yFcb.d.ts.map +0 -1
  247. package/dist/llm-client-BshCREn6.js.map +0 -1
  248. package/dist/llm-judge-C8jMTNNG.js.map +0 -1
  249. package/dist/pareto-BzsxidFD.js.map +0 -1
  250. package/dist/render-C-w2EZ6p.js.map +0 -1
  251. package/dist/schema-DxcKTzbi.js.map +0 -1
  252. package/dist/search-ledger-TUvB5gWH.js.map +0 -1
  253. package/dist/single-run-lock-U58yf8N6.js.map +0 -1
  254. package/dist/skillopt-optimization-method-kyL94-ri.js.map +0 -1
  255. package/dist/summary-report-DM7tlml1.d.ts.map +0 -1
  256. package/dist/tool-groups-DyNz2JZ_.d.ts.map +0 -1
  257. package/dist/tree-BGRglkKd.js.map +0 -1
  258. package/dist/types-D8GhJoHk.d.ts.map +0 -1
@@ -104,229 +104,6 @@ function applyMutation(step, mutation) {
104
104
  return step;
105
105
  }
106
106
  //#endregion
107
- //#region src/trajectory-replay/corpus.ts
108
- /**
109
- * Corpus enumeration for batch replay verification.
110
- *
111
- * A labeled trajectory corpus is a gold label file (array of trajectory
112
- * entries with `incorrect_stages[].incorrect_step_ids`) plus a prepared
113
- * directory:
114
- * <prepared>/normalized/<traj_id>/steps.json normalized steps
115
- * <prepared>/normalized/<traj_id>/task.md optional task statement
116
- * <prepared>/extracted/<traj_id>/swe_raw/** raw mini-SWE trajectory
117
- *
118
- * Only trajectories that record their environment are replayable: the raw
119
- * trajectory must carry `info.docker_config.base_image`. Every excluded case
120
- * is returned with a machine-readable reason — the batch report surfaces the
121
- * full exclusion table, never a silently shrunk denominator.
122
- */
123
- function readLabelEntries(labelsPath) {
124
- const parsed = JSON.parse(readFileSync(labelsPath, "utf8"));
125
- if (!Array.isArray(parsed)) throw new Error(`${labelsPath} is not a JSON array of label entries`);
126
- for (const entry of parsed) if (typeof entry.traj_id !== "string") throw new Error(`${labelsPath} has an entry without a string traj_id`);
127
- return parsed;
128
- }
129
- function goldIncorrectSteps(entry) {
130
- const ids = /* @__PURE__ */ new Set();
131
- for (const stage of entry.incorrect_stages ?? []) for (const id of stage.incorrect_step_ids ?? []) ids.add(id);
132
- return [...ids].sort((a, b) => a - b);
133
- }
134
- function findRawTrajFiles(sweRawDir) {
135
- if (!existsSync(sweRawDir)) return [];
136
- return readdirSync(sweRawDir, {
137
- recursive: true,
138
- encoding: "utf8"
139
- }).filter((relative) => relative.endsWith(".traj.json")).map((relative) => join(sweRawDir, relative)).sort();
140
- }
141
- /** First non-empty output line of the trajectory's first bare `pwd` step. */
142
- function cwdFromPwdObservation(steps) {
143
- for (const step of steps) {
144
- if (step.action.trim() !== "pwd") continue;
145
- const line = parseObservationOutput(step.observation).split("\n").map((l) => l.trim()).find((l) => l.startsWith("/"));
146
- if (line) return line;
147
- }
148
- return null;
149
- }
150
- function taskStatementOf(preparedDir, trajId, raw) {
151
- const taskPath = join(preparedDir, "normalized", trajId, "task.md");
152
- if (existsSync(taskPath)) {
153
- const text = readFileSync(taskPath, "utf8").trim();
154
- if (text.length > 0) return text;
155
- }
156
- const userMessage = raw.messages?.find((m) => m.role === "user");
157
- if (typeof userMessage?.content === "string" && userMessage.content.trim().length > 0) return userMessage.content.trim();
158
- return null;
159
- }
160
- /**
161
- * Resolves the replay resources for one trajectory, independent of gold
162
- * labels — the finding wire uses this with a finding-supplied step instead.
163
- */
164
- function resolveCaseResources(corpus, trajId) {
165
- const sweRawDir = join(corpus.preparedDir, "extracted", trajId, "swe_raw");
166
- const rawFiles = findRawTrajFiles(sweRawDir);
167
- if (rawFiles.length === 0) return {
168
- resolved: false,
169
- reason: "no-swe-raw-trajectory",
170
- detail: sweRawDir
171
- };
172
- if (rawFiles.length > 1) return {
173
- resolved: false,
174
- reason: "ambiguous-swe-raw-trajectory",
175
- detail: rawFiles.join(", ")
176
- };
177
- let raw;
178
- try {
179
- raw = JSON.parse(readFileSync(rawFiles[0], "utf8"));
180
- } catch (err) {
181
- return {
182
- resolved: false,
183
- reason: "unreadable-raw-trajectory",
184
- detail: `${rawFiles[0]}: ${err instanceof Error ? err.message : String(err)}`
185
- };
186
- }
187
- const dockerConfig = raw.info?.docker_config;
188
- const image = dockerConfig?.base_image;
189
- if (typeof image !== "string" || image.length === 0) return {
190
- resolved: false,
191
- reason: "no-docker-image",
192
- detail: rawFiles[0]
193
- };
194
- const stepsPath = join(corpus.preparedDir, "normalized", trajId, "steps.json");
195
- if (!existsSync(stepsPath)) return {
196
- resolved: false,
197
- reason: "missing-steps-json",
198
- detail: stepsPath
199
- };
200
- const steps = JSON.parse(readFileSync(stepsPath, "utf8"));
201
- const runConfigCwd = raw.info?.config?.environment?.cwd;
202
- const dockerCwd = dockerConfig?.cwd;
203
- let cwd;
204
- let cwdSource;
205
- if (typeof runConfigCwd === "string" && runConfigCwd.length > 0) {
206
- cwd = runConfigCwd;
207
- cwdSource = "run-config";
208
- } else if (typeof dockerCwd === "string" && dockerCwd.length > 0) {
209
- cwd = dockerCwd;
210
- cwdSource = "docker-config";
211
- } else {
212
- const observed = cwdFromPwdObservation(steps);
213
- if (!observed) return {
214
- resolved: false,
215
- reason: "cwd-underivable"
216
- };
217
- cwd = observed;
218
- cwdSource = "pwd-observation";
219
- }
220
- const recordedTimeout = raw.info?.config?.environment?.timeout;
221
- return {
222
- resolved: true,
223
- resources: {
224
- corpus: corpus.name,
225
- trajId,
226
- stepsPath,
227
- steps,
228
- taskStatement: taskStatementOf(corpus.preparedDir, trajId, raw),
229
- image,
230
- cwd,
231
- cwdSource,
232
- recordedStepTimeoutMs: typeof recordedTimeout === "number" && recordedTimeout > 0 ? recordedTimeout * 1e3 : null
233
- }
234
- };
235
- }
236
- /**
237
- * Replayable = a raw trajectory with a recorded image AND at least one gold
238
- * incorrect step that is a real mid-trajectory action. Gold steps whose action
239
- * is the submit command are skipped when choosing k (counted per case); a case
240
- * whose golds are ALL submit steps is excluded as gold-only-submit-step.
241
- * Exclusion reasons are reported in resolution order:
242
- * raw trajectory → image → gold labels → steps.json → k range → cwd.
243
- */
244
- function enumerateReplayableCases(corpora) {
245
- const replayable = [];
246
- const excluded = [];
247
- let labelEntryCount = 0;
248
- for (const corpus of corpora) for (const entry of readLabelEntries(corpus.labelsPath)) {
249
- labelEntryCount += 1;
250
- const trajId = entry.traj_id;
251
- const resolution = resolveCaseResources(corpus, trajId);
252
- if (!resolution.resolved) {
253
- excluded.push({
254
- corpus: corpus.name,
255
- trajId,
256
- reason: resolution.reason,
257
- detail: resolution.detail
258
- });
259
- continue;
260
- }
261
- const gold = goldIncorrectSteps(entry);
262
- if (gold.length === 0) {
263
- excluded.push({
264
- corpus: corpus.name,
265
- trajId,
266
- reason: "no-gold-incorrect-step"
267
- });
268
- continue;
269
- }
270
- const resources = resolution.resources;
271
- let submitGoldsSkipped = 0;
272
- let target = null;
273
- let missingGoldId = null;
274
- for (const goldId of gold) {
275
- const step = resources.steps.find((s) => s.step_id === goldId);
276
- if (!step) {
277
- missingGoldId = goldId;
278
- break;
279
- }
280
- if (step.action.trim() === "submit" || isSubmitOnlyAction(step.action)) {
281
- submitGoldsSkipped += 1;
282
- continue;
283
- }
284
- target = step;
285
- break;
286
- }
287
- if (missingGoldId !== null) {
288
- excluded.push({
289
- corpus: corpus.name,
290
- trajId,
291
- reason: "gold-step-outside-steps",
292
- detail: `k=${missingGoldId}, steps=${resources.steps.length}`
293
- });
294
- continue;
295
- }
296
- if (!target) {
297
- excluded.push({
298
- corpus: corpus.name,
299
- trajId,
300
- reason: "gold-only-submit-step",
301
- detail: `${submitGoldsSkipped} gold step(s), all submit commands`
302
- });
303
- continue;
304
- }
305
- const recordedReturncodeAtK = parseRecordedReturncode(target.observation);
306
- if (recordedReturncodeAtK === null) {
307
- excluded.push({
308
- corpus: corpus.name,
309
- trajId,
310
- reason: "no-recorded-returncode-at-k",
311
- detail: `k=${target.step_id}; arm A cannot confirm execution without a recorded returncode`
312
- });
313
- continue;
314
- }
315
- replayable.push({
316
- ...resources,
317
- goldIncorrectSteps: gold,
318
- k: target.step_id,
319
- submitGoldsSkipped,
320
- recordedReturncodeAtK
321
- });
322
- }
323
- return {
324
- replayable,
325
- excluded,
326
- labelEntryCount
327
- };
328
- }
329
- //#endregion
330
107
  //#region src/trajectory-replay/fix.ts
331
108
  /**
332
109
  * Counterfactual patch synthesis: turn a recorded incorrect step into a
@@ -604,129 +381,9 @@ async function runFixLoop(caller, input, executor, options) {
604
381
  };
605
382
  }
606
383
  //#endregion
607
- //#region src/trajectory-replay/image-preparer.ts
384
+ //#region src/trajectory-replay/verify.ts
608
385
  /**
609
- * Replay-ready image derivation.
610
- *
611
- * A recorded trajectory names the image it ran in, but the image is not always
612
- * runnable as-is: sandbox platforms pin customer commands to a non-root
613
- * identity, so a root-owned working tree must be chowned before the replay can
614
- * write to it. `ImagePreparer` is that step, injectable so a consumer whose
615
- * images are already replay-ready supplies its own no-op or none at all.
616
- */
617
- const execFileAsync = promisify(execFile);
618
- function derivedImageTag(image, cwd) {
619
- return `ctb-replay:${createHash("sha256").update(`${image}\n${cwd}`).digest("hex").slice(0, 12)}-uid1000`;
620
- }
621
- /**
622
- * Pulls the base image when absent and builds `FROM <base>; RUN chown -R
623
- * 1000:1000 <cwd>` tagged by content hash, so repeated batches reuse both
624
- * the pull and the build. cwd `/` skips the chown (never chown -R /) and
625
- * replays on the base image directly.
626
- */
627
- function dockerImagePreparer(options = {}) {
628
- const pullTimeoutMs = options.pullTimeoutMs ?? 12e5;
629
- const buildTimeoutMs = options.buildTimeoutMs ?? 9e5;
630
- const imageExists = async (tag) => {
631
- try {
632
- await execFileAsync("docker", [
633
- "image",
634
- "inspect",
635
- tag
636
- ], { maxBuffer: 8388608 });
637
- return true;
638
- } catch {
639
- return false;
640
- }
641
- };
642
- const errorTail = (err) => {
643
- return (err && typeof err === "object" && "stderr" in err && typeof err.stderr === "string" && err.stderr.length > 0 ? err.stderr : err instanceof Error ? err.message : String(err)).trim().split("\n").slice(-3).join(" | ").slice(0, 400);
644
- };
645
- return { async ensure(image, cwd) {
646
- if (cwd === "/") {
647
- if (await imageExists(image)) return {
648
- succeeded: true,
649
- value: {
650
- derivedImage: image,
651
- pulled: false,
652
- built: false
653
- }
654
- };
655
- try {
656
- await execFileAsync("docker", ["pull", image], {
657
- timeout: pullTimeoutMs,
658
- maxBuffer: 33554432
659
- });
660
- } catch (err) {
661
- return {
662
- succeeded: false,
663
- error: `pull ${image}: ${errorTail(err)}`
664
- };
665
- }
666
- return {
667
- succeeded: true,
668
- value: {
669
- derivedImage: image,
670
- pulled: true,
671
- built: false
672
- }
673
- };
674
- }
675
- const derived = derivedImageTag(image, cwd);
676
- if (await imageExists(derived)) return {
677
- succeeded: true,
678
- value: {
679
- derivedImage: derived,
680
- pulled: false,
681
- built: false
682
- }
683
- };
684
- let pulled = false;
685
- if (!await imageExists(image)) try {
686
- await execFileAsync("docker", ["pull", image], {
687
- timeout: pullTimeoutMs,
688
- maxBuffer: 33554432
689
- });
690
- pulled = true;
691
- } catch (err) {
692
- return {
693
- succeeded: false,
694
- error: `pull ${image}: ${errorTail(err)}`
695
- };
696
- }
697
- const contextDir = mkdtempSync(join(tmpdir(), "ctb-replay-image-"));
698
- const quotedCwd = `'${cwd.replaceAll("'", `'\\''`)}'`;
699
- writeFileSync(join(contextDir, "Dockerfile"), `FROM ${image}\nRUN chown -R 1000:1000 ${quotedCwd}\n`);
700
- try {
701
- await execFileAsync("docker", [
702
- "build",
703
- "-t",
704
- derived,
705
- contextDir
706
- ], {
707
- timeout: buildTimeoutMs,
708
- maxBuffer: 33554432
709
- });
710
- } catch (err) {
711
- return {
712
- succeeded: false,
713
- error: `build ${derived} from ${image}: ${errorTail(err)}`
714
- };
715
- }
716
- return {
717
- succeeded: true,
718
- value: {
719
- derivedImage: derived,
720
- pulled,
721
- built: true
722
- }
723
- };
724
- } };
725
- }
726
- //#endregion
727
- //#region src/trajectory-replay/verify.ts
728
- /**
729
- * Execution-replay verification for recorded shell trajectories.
386
+ * Execution-replay verification for recorded shell trajectories.
730
387
  *
731
388
  * Turns a cited claim ("step k is the error-critical step") into an executed
732
389
  * proof: replay steps 1..k-1 inside the trajectory's own image, then
@@ -1116,33 +773,8 @@ function renderReport(verdict, target, armA, armB) {
1116
773
  return lines.join("\n");
1117
774
  }
1118
775
  //#endregion
1119
- //#region src/trajectory-replay/batch.ts
1120
- /**
1121
- * Batch replay verification across gold-labeled trajectory corpora.
1122
- *
1123
- * For every replayable case (a raw trajectory with a recorded image AND at
1124
- * least one gold incorrect step) the batch:
1125
- * 1. derives the replay-ready image through the injected `ImagePreparer`,
1126
- * 2. replays the prefix and runs arm A — the recorded gold step k, and
1127
- * 3. optionally generates a corrected command with one LLM call and runs
1128
- * arm B in its own fresh session.
1129
- *
1130
- * Headline metrics:
1131
- * replayability rate — fraction of replayable cases where the prefix
1132
- * replays within the divergence tolerance AND arm A reproduces a
1133
- * recorded failure at k; a zero exit without an error signature is not
1134
- * evidence of failure;
1135
- * prefix fidelity — executed prefix steps and the share of them that did
1136
- * not confirm the recording, split by kind. A corpus whose recordings
1137
- * carry no returncodes shows up here as unknown-expectation steps, never
1138
- * as a clean replay;
1139
- * fix-flip rate — fraction of arm-B-executed cases where the failure
1140
- * vanished (exit 0, signature absent).
1141
- *
1142
- * Image pulls and execs run strictly serially: pulls contend on disk and
1143
- * registry bandwidth, and serial cases keep wall-time attribution per case
1144
- * honest. Pull failures are report rows, never silent skips.
1145
- */
776
+ //#region src/trajectory-replay/batch-fix.ts
777
+ /** Arm B and the counterfactual fix phase of a replay batch. */
1146
778
  function seededSample(items, size, seed) {
1147
779
  const pool = [...items];
1148
780
  const random = mulberry32(seed);
@@ -1195,471 +827,228 @@ async function executeArmB(replayCase, fixCommand, backend, signature, stepTimeo
1195
827
  stderr: exec.stderr
1196
828
  };
1197
829
  }
1198
- function rate(numerator, denominator) {
1199
- return denominator > 0 ? numerator / denominator : null;
830
+ /** A fix outcome with nothing measured; callers name what they did measure. */
831
+ function fixResult(measured) {
832
+ return {
833
+ attempted: true,
834
+ sampledOut: false,
835
+ command: null,
836
+ llmError: null,
837
+ usage: null,
838
+ armBExit: null,
839
+ armBPrefixExecuted: null,
840
+ armBPrefixDivergences: null,
841
+ armBPrefixDivergencePct: null,
842
+ failureVanished: null,
843
+ armBError: null,
844
+ attempts: null,
845
+ flippedAtAttempt: null,
846
+ ...measured
847
+ };
1200
848
  }
1201
- async function runReplayBatch(options) {
1202
- const onProgress = options.onProgress ?? (() => {});
1203
- const enumeration = enumerateReplayableCases(options.corpora);
1204
- let selected = enumeration.replayable;
1205
- if (options.caseFilter) selected = selected.filter((c) => c.trajId.includes(options.caseFilter));
1206
- if (options.caseLimit !== void 0) selected = selected.slice(0, options.caseLimit);
1207
- onProgress(`enumerated ${enumeration.labelEntryCount} label entries → ${enumeration.replayable.length} replayable, ${enumeration.excluded.length} excluded; executing ${selected.length}`);
1208
- const preparer = options.preparer ?? dockerImagePreparer();
1209
- const backendFactory = options.backendFactory;
1210
- mkdirSync(options.out, { recursive: true });
1211
- const progressPath = join(options.out, "cases.jsonl");
1212
- const rows = [];
1213
- const pullFailures = [];
1214
- const verdictByTraj = /* @__PURE__ */ new Map();
1215
- for (const [index, replayCase] of selected.entries()) {
1216
- const caseStart = Date.now();
1217
- const label = `[${index + 1}/${selected.length}] ${replayCase.corpus}/${replayCase.trajId}`;
1218
- onProgress(`${label}: preparing image ${replayCase.image}`);
1219
- const preparation = await preparer.ensure(replayCase.image, replayCase.cwd);
1220
- const base = {
1221
- corpus: replayCase.corpus,
1222
- trajId: replayCase.trajId,
1223
- image: replayCase.image,
1224
- cwd: replayCase.cwd,
1225
- cwdSource: replayCase.cwdSource,
1226
- k: replayCase.k,
1227
- stepCount: replayCase.steps.length,
1228
- goldIncorrectSteps: replayCase.goldIncorrectSteps,
1229
- submitGoldsSkipped: replayCase.submitGoldsSkipped,
1230
- recordedReturncodeAtK: replayCase.recordedReturncodeAtK
849
+ function runArmB(run, fixCase, command, label) {
850
+ const { options } = run;
851
+ return executeArmB(fixCase.replayCase, command, options.backendFactory(fixCase.row.derivedImage), fixCase.signature, fixCase.stepTimeoutMs, options.prefixLimit, (message) => run.onProgress(`${label}: ${message}`));
852
+ }
853
+ /** The fix summary a finished loop reports: the flipping attempt, else the last executed one, else why it stopped. */
854
+ function loopFixResult(result, usage) {
855
+ const summary = (result.flippedAtAttempt !== null ? result.attempts.find((a) => a.attempt === result.flippedAtAttempt) : null) ?? [...result.attempts].reverse().find((a) => a.executed) ?? null;
856
+ const lastRecord = result.attempts.at(-1) ?? null;
857
+ if (summary) return fixResult({
858
+ command: summary.command,
859
+ usage,
860
+ armBExit: summary.exitCode,
861
+ armBPrefixExecuted: summary.prefixExecuted,
862
+ armBPrefixDivergences: summary.prefixDivergences,
863
+ armBPrefixDivergencePct: summary.prefixDivergencePct,
864
+ failureVanished: summary.failureVanished,
865
+ attempts: result.attempts,
866
+ flippedAtAttempt: result.flippedAtAttempt
867
+ });
868
+ if (result.aborted) return fixResult({
869
+ command: lastRecord?.command ?? null,
870
+ usage,
871
+ armBError: lastRecord?.armBError ?? "sandbox error",
872
+ attempts: result.attempts
873
+ });
874
+ return fixResult({
875
+ llmError: lastRecord?.llmError ?? "no attempt produced a runnable fix",
876
+ usage,
877
+ attempts: result.attempts
878
+ });
879
+ }
880
+ /** Iterative fix: failed arms feed their real output into up to `fixAttempts` prompts. */
881
+ async function fixByLoop(run, fixCase, caller, tally) {
882
+ const { options, onProgress } = run;
883
+ const { row, label, caseOut } = fixCase;
884
+ const fixAttempts = options.fixAttempts ?? 3;
885
+ onProgress(`${label}: fix loop (budget ${fixAttempts} attempts)`);
886
+ const result = await runFixLoop(caller, fixCase.promptInput, async (command, attempt) => {
887
+ onProgress(`${label}: attempt ${attempt} arm B — ${command.split("\n")[0].slice(0, 120)}`);
888
+ const armB = await runArmB(run, fixCase, command, `${label}: attempt ${attempt}`);
889
+ mkdirSync(caseOut, { recursive: true });
890
+ writeFileSync(join(caseOut, `armB-attempt${attempt}-result.json`), `${JSON.stringify({
891
+ command,
892
+ ...armB
893
+ }, null, 2)}\n`);
894
+ writeFileSync(join(caseOut, "armB-result.json"), `${JSON.stringify({
895
+ command,
896
+ attempt,
897
+ ...armB
898
+ }, null, 2)}\n`);
899
+ return armB;
900
+ }, {
901
+ maxAttempts: fixAttempts,
902
+ onProgress: (message) => onProgress(`${label}: ${message}`)
903
+ });
904
+ tally.calls += result.llmCalls;
905
+ tally.failures += result.llmFailures;
906
+ tally.promptTokens += result.promptTokens;
907
+ tally.completionTokens += result.completionTokens;
908
+ tally.callsWithoutUsage += result.callsWithoutUsage;
909
+ const usage = result.promptTokens + result.completionTokens > 0 ? {
910
+ promptTokens: result.promptTokens,
911
+ completionTokens: result.completionTokens
912
+ } : null;
913
+ onProgress(`${label}: loop done — flipped=${result.flipped}` + (result.flippedAtAttempt !== null ? ` at attempt ${result.flippedAtAttempt}` : "") + ` (${result.llmCalls} calls, ${result.attempts.filter((a) => a.executed).length} arms)`);
914
+ return {
915
+ ...row,
916
+ fix: loopFixResult(result, usage)
917
+ };
918
+ }
919
+ /** One-shot fix: a single generated command, one arm B. */
920
+ async function fixByOneShot(run, fixCase, caller, tally) {
921
+ const { onProgress } = run;
922
+ const { row, label, caseOut } = fixCase;
923
+ onProgress(`${label}: generating corrected command`);
924
+ tally.calls += 1;
925
+ const generated = await generateFixCommand(caller, fixCase.promptInput);
926
+ if (!generated.succeeded) {
927
+ tally.failures += 1;
928
+ onProgress(`${label}: LLM failed — ${generated.error.slice(0, 200)}`);
929
+ return {
930
+ ...row,
931
+ fix: fixResult({ llmError: generated.error })
1231
932
  };
1232
- if (!preparation.succeeded) {
1233
- pullFailures.push({
1234
- corpus: replayCase.corpus,
1235
- trajId: replayCase.trajId,
1236
- image: replayCase.image,
1237
- error: preparation.error
1238
- });
1239
- const row = {
1240
- ...base,
1241
- derivedImage: null,
1242
- signature: null,
1243
- status: "image-unavailable",
1244
- error: preparation.error,
1245
- imagePulled: false,
1246
- imageBuilt: false,
1247
- prefixExecuted: null,
1248
- prefixDivergences: null,
1249
- prefixDivergencePct: null,
1250
- prefixConfirmed: null,
1251
- prefixReturncodeMismatches: null,
1252
- prefixUnknownExpectations: null,
1253
- armAExit: null,
1254
- armAReturncodeMatch: false,
1255
- armASignatureMatch: false,
1256
- replayed: false,
1257
- fix: null,
1258
- wallMs: Date.now() - caseStart
933
+ }
934
+ if (generated.value.usage === null || generated.value.usage === void 0) tally.callsWithoutUsage += 1;
935
+ tally.promptTokens += generated.value.usage?.promptTokens ?? 0;
936
+ tally.completionTokens += generated.value.usage?.completionTokens ?? 0;
937
+ onProgress(`${label}: arm B — ${generated.value.command.split("\n")[0].slice(0, 120)}`);
938
+ try {
939
+ const armB = await runArmB(run, fixCase, generated.value.command, label);
940
+ const fix = fixResult({
941
+ command: generated.value.command,
942
+ usage: generated.value.usage,
943
+ armBExit: armB.exitCode,
944
+ armBPrefixExecuted: armB.prefixExecuted,
945
+ armBPrefixDivergences: armB.prefixDivergences,
946
+ armBPrefixDivergencePct: armB.prefixDivergencePct,
947
+ failureVanished: armB.failureVanished,
948
+ flippedAtAttempt: armB.failureVanished ? 1 : null
949
+ });
950
+ mkdirSync(caseOut, { recursive: true });
951
+ writeFileSync(join(caseOut, "armB-result.json"), `${JSON.stringify({
952
+ command: generated.value.command,
953
+ ...armB
954
+ }, null, 2)}\n`);
955
+ onProgress(`${label}: armB exit=${armB.exitCode} failureVanished=${armB.failureVanished}`);
956
+ return {
957
+ ...row,
958
+ fix
959
+ };
960
+ } catch (err) {
961
+ const message = err instanceof Error ? err.message : String(err);
962
+ onProgress(`${label}: arm B error — ${message.slice(0, 200)}`);
963
+ return {
964
+ ...row,
965
+ fix: fixResult({
966
+ command: generated.value.command,
967
+ usage: generated.value.usage,
968
+ armBError: message.slice(0, 500)
969
+ })
970
+ };
971
+ }
972
+ }
973
+ /** Phase 2: counterfactual fixes for the cases where arm A reproduced, capped and seeded-sampled. */
974
+ async function runFixPhase(run, selected, rows, verdicts) {
975
+ const { options, onProgress } = run;
976
+ const caller = options.fixCaller;
977
+ if (!caller) throw new Error(`trajectory-replay: fix=${options.fix} requires a fixCaller`);
978
+ const maxFixCases = options.maxFixCases ?? 30;
979
+ const seed = options.seed ?? 17;
980
+ const eligible = rows.filter((r) => r.status === "ok" && r.replayed);
981
+ const sampled = eligible.length > maxFixCases ? seededSample(eligible, maxFixCases, seed) : new Set(eligible);
982
+ if (eligible.length > maxFixCases) onProgress(`fix phase: ${eligible.length} eligible > cap ${maxFixCases}; seeded sample (seed=${seed})`);
983
+ const tally = {
984
+ calls: 0,
985
+ failures: 0,
986
+ promptTokens: 0,
987
+ completionTokens: 0,
988
+ callsWithoutUsage: 0
989
+ };
990
+ for (const row of eligible) {
991
+ const index = rows.indexOf(row);
992
+ if (!sampled.has(row)) {
993
+ rows[index] = {
994
+ ...row,
995
+ fix: fixResult({
996
+ attempted: false,
997
+ sampledOut: true
998
+ })
1259
999
  };
1260
- rows.push(row);
1261
- appendFileSync(progressPath, `${JSON.stringify(row)}\n`);
1262
- onProgress(`${label}: image unavailable — ${preparation.error}`);
1263
1000
  continue;
1264
1001
  }
1265
- const { derivedImage, pulled, built } = preparation.value;
1266
- const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
1267
- const caseOut = join(options.out, caseOutDirName(replayCase));
1268
- onProgress(`${label}: arm A on ${derivedImage} (k=${replayCase.k}, timeout ${stepTimeoutMs}ms)`);
1269
- try {
1270
- const verdict = await replayVerify({
1271
- stepsPath: replayCase.stepsPath,
1272
- image: derivedImage,
1273
- at: replayCase.k,
1274
- cwd: replayCase.cwd,
1275
- out: caseOut,
1276
- caseId: replayCase.trajId,
1277
- stepTimeoutMs,
1278
- prefixLimit: options.prefixLimit,
1279
- backend: backendFactory(derivedImage),
1280
- onProgress: (message) => onProgress(`${label}: ${message}`)
1281
- });
1282
- verdictByTraj.set(replayCase.trajId, verdict);
1283
- const returncodeMatch = verdict.recordedReturncode !== null && verdict.armA.exitCode === verdict.recordedReturncode;
1284
- const row = {
1285
- ...base,
1286
- derivedImage,
1287
- signature: verdict.signature,
1288
- status: "ok",
1289
- error: null,
1290
- imagePulled: pulled,
1291
- imageBuilt: built,
1292
- prefixExecuted: verdict.prefixExecuted,
1293
- prefixDivergences: verdict.prefixDivergences.length,
1294
- prefixDivergencePct: verdict.prefixDivergencePct,
1295
- prefixConfirmed: verdict.prefixConfirmed,
1296
- prefixReturncodeMismatches: verdict.prefixReturncodeMismatches,
1297
- prefixUnknownExpectations: verdict.prefixUnknownExpectations,
1298
- armAExit: verdict.armA.exitCode,
1299
- armAReturncodeMatch: returncodeMatch,
1300
- armASignatureMatch: verdict.armA.failureSignatureMatch,
1301
- replayed: verdict.valid,
1302
- fix: null,
1303
- wallMs: Date.now() - caseStart
1304
- };
1305
- rows.push(row);
1306
- appendFileSync(progressPath, `${JSON.stringify(row)}\n`);
1307
- onProgress(`${label}: armA exit=${verdict.armA.exitCode} rcMatch=${returncodeMatch} divergences=${verdict.prefixDivergences.length}/${verdict.prefixExecuted} (${verdict.prefixDivergencePct}%, ${verdict.prefixUnknownExpectations} unknown-expectation)`);
1308
- } catch (err) {
1309
- const message = err instanceof Error ? err.message : String(err);
1310
- const row = {
1311
- ...base,
1312
- derivedImage,
1313
- signature: null,
1314
- status: "replay-error",
1315
- error: message.slice(0, 500),
1316
- imagePulled: pulled,
1317
- imageBuilt: built,
1318
- prefixExecuted: null,
1319
- prefixDivergences: null,
1320
- prefixDivergencePct: null,
1321
- prefixConfirmed: null,
1322
- prefixReturncodeMismatches: null,
1323
- prefixUnknownExpectations: null,
1324
- armAExit: null,
1325
- armAReturncodeMatch: false,
1326
- armASignatureMatch: false,
1327
- replayed: false,
1328
- fix: null,
1329
- wallMs: Date.now() - caseStart
1330
- };
1331
- rows.push(row);
1332
- appendFileSync(progressPath, `${JSON.stringify(row)}\n`);
1333
- onProgress(`${label}: replay error — ${message.slice(0, 200)}`);
1334
- }
1335
- }
1336
- let llm = null;
1337
- if (options.fix !== "none") {
1338
- const caller = options.fixCaller;
1339
- if (!caller) throw new Error(`trajectory-replay: fix=${options.fix} requires a fixCaller`);
1340
- const fixAttempts = options.fixAttempts ?? 3;
1341
- const maxFixCases = options.maxFixCases ?? 30;
1342
- const seed = options.seed ?? 17;
1343
- const eligible = rows.filter((r) => r.status === "ok" && r.replayed);
1344
- const sampled = eligible.length > maxFixCases ? seededSample(eligible, maxFixCases, seed) : new Set(eligible);
1345
- if (eligible.length > maxFixCases) onProgress(`fix phase: ${eligible.length} eligible > cap ${maxFixCases}; seeded sample (seed=${seed})`);
1346
- let calls = 0;
1347
- let failures = 0;
1348
- let promptTokens = 0;
1349
- let completionTokens = 0;
1350
- let callsWithoutUsage = 0;
1351
- for (const row of eligible) {
1352
- const index = rows.indexOf(row);
1353
- if (!sampled.has(row)) {
1354
- rows[index] = {
1355
- ...row,
1356
- fix: {
1357
- attempted: false,
1358
- sampledOut: true,
1359
- command: null,
1360
- llmError: null,
1361
- usage: null,
1362
- armBExit: null,
1363
- armBPrefixExecuted: null,
1364
- armBPrefixDivergences: null,
1365
- armBPrefixDivergencePct: null,
1366
- failureVanished: null,
1367
- armBError: null,
1368
- attempts: null,
1369
- flippedAtAttempt: null
1370
- }
1371
- };
1372
- continue;
1373
- }
1374
- const replayCase = selected.find((c) => c.trajId === row.trajId && c.corpus === row.corpus);
1375
- const label = `fix ${row.corpus}/${row.trajId}`;
1376
- const signature = verdictByTraj.get(row.trajId)?.signature ?? null;
1377
- const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
1378
- const promptInput = {
1002
+ const replayCase = selected.find((c) => c.trajId === row.trajId && c.corpus === row.corpus);
1003
+ const fixCase = {
1004
+ row,
1005
+ replayCase,
1006
+ label: `fix ${row.corpus}/${row.trajId}`,
1007
+ signature: verdicts.get(row.trajId)?.signature ?? null,
1008
+ stepTimeoutMs: options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4,
1009
+ caseOut: join(options.out, caseOutDirName(row)),
1010
+ promptInput: {
1379
1011
  taskStatement: replayCase.taskStatement,
1380
1012
  steps: replayCase.steps,
1381
1013
  k: replayCase.k
1382
- };
1383
- const caseOut = join(options.out, caseOutDirName(row));
1384
- if (options.fix === "loop") {
1385
- onProgress(`${label}: fix loop (budget ${fixAttempts} attempts)`);
1386
- const result = await runFixLoop(caller, promptInput, async (command, attempt) => {
1387
- onProgress(`${label}: attempt ${attempt} arm B — ${command.split("\n")[0].slice(0, 120)}`);
1388
- const armB = await executeArmB(replayCase, command, backendFactory(row.derivedImage), signature, stepTimeoutMs, options.prefixLimit, (message) => onProgress(`${label}: attempt ${attempt}: ${message}`));
1389
- mkdirSync(caseOut, { recursive: true });
1390
- writeFileSync(join(caseOut, `armB-attempt${attempt}-result.json`), `${JSON.stringify({
1391
- command,
1392
- ...armB
1393
- }, null, 2)}\n`);
1394
- writeFileSync(join(caseOut, "armB-result.json"), `${JSON.stringify({
1395
- command,
1396
- attempt,
1397
- ...armB
1398
- }, null, 2)}\n`);
1399
- return armB;
1400
- }, {
1401
- maxAttempts: fixAttempts,
1402
- onProgress: (message) => onProgress(`${label}: ${message}`)
1403
- });
1404
- calls += result.llmCalls;
1405
- failures += result.llmFailures;
1406
- promptTokens += result.promptTokens;
1407
- completionTokens += result.completionTokens;
1408
- callsWithoutUsage += result.callsWithoutUsage;
1409
- const usage = result.promptTokens + result.completionTokens > 0 ? {
1410
- promptTokens: result.promptTokens,
1411
- completionTokens: result.completionTokens
1412
- } : null;
1413
- const summary = (result.flippedAtAttempt !== null ? result.attempts.find((a) => a.attempt === result.flippedAtAttempt) : null) ?? [...result.attempts].reverse().find((a) => a.executed) ?? null;
1414
- const lastRecord = result.attempts.at(-1) ?? null;
1415
- rows[index] = {
1416
- ...row,
1417
- fix: summary ? {
1418
- attempted: true,
1419
- sampledOut: false,
1420
- command: summary.command,
1421
- llmError: null,
1422
- usage,
1423
- armBExit: summary.exitCode,
1424
- armBPrefixExecuted: summary.prefixExecuted,
1425
- armBPrefixDivergences: summary.prefixDivergences,
1426
- armBPrefixDivergencePct: summary.prefixDivergencePct,
1427
- failureVanished: summary.failureVanished,
1428
- armBError: null,
1429
- attempts: result.attempts,
1430
- flippedAtAttempt: result.flippedAtAttempt
1431
- } : result.aborted ? {
1432
- attempted: true,
1433
- sampledOut: false,
1434
- command: lastRecord?.command ?? null,
1435
- llmError: null,
1436
- usage,
1437
- armBExit: null,
1438
- armBPrefixExecuted: null,
1439
- armBPrefixDivergences: null,
1440
- armBPrefixDivergencePct: null,
1441
- failureVanished: null,
1442
- armBError: lastRecord?.armBError ?? "sandbox error",
1443
- attempts: result.attempts,
1444
- flippedAtAttempt: null
1445
- } : {
1446
- attempted: true,
1447
- sampledOut: false,
1448
- command: null,
1449
- llmError: lastRecord?.llmError ?? "no attempt produced a runnable fix",
1450
- usage,
1451
- armBExit: null,
1452
- armBPrefixExecuted: null,
1453
- armBPrefixDivergences: null,
1454
- armBPrefixDivergencePct: null,
1455
- failureVanished: null,
1456
- armBError: null,
1457
- attempts: result.attempts,
1458
- flippedAtAttempt: null
1459
- }
1460
- };
1461
- onProgress(`${label}: loop done — flipped=${result.flipped}` + (result.flippedAtAttempt !== null ? ` at attempt ${result.flippedAtAttempt}` : "") + ` (${result.llmCalls} calls, ${result.attempts.filter((a) => a.executed).length} arms)`);
1462
- continue;
1463
- }
1464
- onProgress(`${label}: generating corrected command`);
1465
- calls += 1;
1466
- const generated = await generateFixCommand(caller, promptInput);
1467
- if (!generated.succeeded) {
1468
- failures += 1;
1469
- rows[index] = {
1470
- ...row,
1471
- fix: {
1472
- attempted: true,
1473
- sampledOut: false,
1474
- command: null,
1475
- llmError: generated.error,
1476
- usage: null,
1477
- armBExit: null,
1478
- armBPrefixExecuted: null,
1479
- armBPrefixDivergences: null,
1480
- armBPrefixDivergencePct: null,
1481
- failureVanished: null,
1482
- armBError: null,
1483
- attempts: null,
1484
- flippedAtAttempt: null
1485
- }
1486
- };
1487
- onProgress(`${label}: LLM failed — ${generated.error.slice(0, 200)}`);
1488
- continue;
1489
- }
1490
- if (generated.value.usage === null || generated.value.usage === void 0) callsWithoutUsage += 1;
1491
- promptTokens += generated.value.usage?.promptTokens ?? 0;
1492
- completionTokens += generated.value.usage?.completionTokens ?? 0;
1493
- onProgress(`${label}: arm B — ${generated.value.command.split("\n")[0].slice(0, 120)}`);
1494
- try {
1495
- const armB = await executeArmB(replayCase, generated.value.command, backendFactory(row.derivedImage), signature, stepTimeoutMs, options.prefixLimit, (message) => onProgress(`${label}: ${message}`));
1496
- rows[index] = {
1497
- ...row,
1498
- fix: {
1499
- attempted: true,
1500
- sampledOut: false,
1501
- command: generated.value.command,
1502
- llmError: null,
1503
- usage: generated.value.usage,
1504
- armBExit: armB.exitCode,
1505
- armBPrefixExecuted: armB.prefixExecuted,
1506
- armBPrefixDivergences: armB.prefixDivergences,
1507
- armBPrefixDivergencePct: armB.prefixDivergencePct,
1508
- failureVanished: armB.failureVanished,
1509
- armBError: null,
1510
- attempts: null,
1511
- flippedAtAttempt: armB.failureVanished ? 1 : null
1512
- }
1513
- };
1514
- mkdirSync(caseOut, { recursive: true });
1515
- writeFileSync(join(caseOut, "armB-result.json"), `${JSON.stringify({
1516
- command: generated.value.command,
1517
- ...armB
1518
- }, null, 2)}\n`);
1519
- onProgress(`${label}: armB exit=${armB.exitCode} failureVanished=${armB.failureVanished}`);
1520
- } catch (err) {
1521
- const message = err instanceof Error ? err.message : String(err);
1522
- rows[index] = {
1523
- ...row,
1524
- fix: {
1525
- attempted: true,
1526
- sampledOut: false,
1527
- command: generated.value.command,
1528
- llmError: null,
1529
- usage: generated.value.usage,
1530
- armBExit: null,
1531
- armBPrefixExecuted: null,
1532
- armBPrefixDivergences: null,
1533
- armBPrefixDivergencePct: null,
1534
- failureVanished: null,
1535
- armBError: message.slice(0, 500),
1536
- attempts: null,
1537
- flippedAtAttempt: null
1538
- }
1539
- };
1540
- onProgress(`${label}: arm B error — ${message.slice(0, 200)}`);
1541
1014
  }
1542
- }
1543
- llm = {
1544
- model: options.fixModelLabel ?? "unknown",
1545
- calls,
1546
- failures,
1547
- promptTokens,
1548
- completionTokens,
1549
- callsWithoutUsage
1550
1015
  };
1016
+ rows[index] = options.fix === "loop" ? await fixByLoop(run, fixCase, caller, tally) : await fixByOneShot(run, fixCase, caller, tally);
1551
1017
  }
1552
- const executedRows = rows.filter((r) => r.status === "ok");
1553
- const replayed = executedRows.filter((r) => r.replayed);
1554
- const sumOver = (pick) => executedRows.reduce((total, row) => total + (pick(row) ?? 0), 0);
1555
- const executedSteps = sumOver((r) => r.prefixExecuted);
1556
- const divergentSteps = sumOver((r) => r.prefixDivergences);
1557
- const prefixFidelity = {
1558
- executedSteps,
1559
- divergentSteps,
1560
- returncodeMismatches: sumOver((r) => r.prefixReturncodeMismatches),
1561
- unknownExpectations: sumOver((r) => r.prefixUnknownExpectations),
1562
- divergencePct: executedSteps > 0 ? Number((divergentSteps / executedSteps * 100).toFixed(1)) : null,
1563
- tolerancePct: 10,
1564
- casesWithinTolerance: executedRows.filter((r) => r.prefixDivergencePct !== null && r.prefixDivergencePct <= 10).length,
1565
- casesExecuted: executedRows.length
1018
+ return {
1019
+ model: options.fixModelLabel ?? "unknown",
1020
+ ...tally
1566
1021
  };
1567
- const signatureStrict = executedRows.filter((r) => r.replayed && r.armASignatureMatch);
1568
- const armBExecuted = rows.filter((r) => r.fix?.attempted && r.fix.failureVanished !== null);
1569
- const flipped = armBExecuted.filter((r) => r.fix.failureVanished === true);
1570
- const armBNonzeroRc = armBExecuted.filter((r) => r.recordedReturncodeAtK !== null && r.recordedReturncodeAtK !== 0);
1571
- const flippedNonzeroRc = armBNonzeroRc.filter((r) => r.fix.failureVanished === true);
1572
- const attempt1Executed = rows.filter((r) => r.fix?.attempts?.find((a) => a.attempt === 1)?.executed === true);
1573
- const flippedAt1 = rows.filter((r) => r.fix?.flippedAtAttempt === 1);
1574
- const flipsByAttempt = {};
1575
- for (const row of rows) {
1576
- const at = row.fix?.flippedAtAttempt;
1577
- if (typeof at === "number") flipsByAttempt[String(at)] = (flipsByAttempt[String(at)] ?? 0) + 1;
1022
+ }
1023
+ //#endregion
1024
+ //#region src/trajectory-replay/batch-report.ts
1025
+ function pct(value) {
1026
+ return value === null ? "—" : `${(value * 100).toFixed(1)}%`;
1027
+ }
1028
+ /** The title and the headline rates, prefix divergence and fix-flip numbers. */
1029
+ function pushHeadline(report, lines) {
1030
+ const { headline } = report;
1031
+ lines.push("# Replay-verify batch report");
1032
+ lines.push("");
1033
+ lines.push(`Generated ${report.generatedAt}.`);
1034
+ lines.push("");
1035
+ lines.push("## Headline");
1036
+ lines.push("");
1037
+ const fidelity = headline.prefixFidelity;
1038
+ lines.push(`- **Replayability rate: ${pct(headline.replayabilityRate.value)}** (${headline.replayabilityRate.numerator}/${headline.replayabilityRate.denominator} replayable cases where the prefix replayed within ${fidelity.tolerancePct}% divergence AND arm A reproduced an observed failure at the gold step k).`);
1039
+ lines.push(`- Signature-strict rate: ${pct(headline.signatureStrictRate.value)} (${headline.signatureStrictRate.numerator}/${headline.signatureStrictRate.denominator}; additionally requires the recorded error substring in arm A output).`);
1040
+ lines.push(`- **Prefix divergence: ${fidelity.divergencePct === null ? "—" : `${fidelity.divergencePct}%`}** (${fidelity.divergentSteps}/${fidelity.executedSteps} executed prefix steps did not confirm the recording: ${fidelity.returncodeMismatches} returncode mismatches, ${fidelity.unknownExpectations} with no recorded returncode to check). ${fidelity.casesWithinTolerance}/${fidelity.casesExecuted} executed cases are within the ${fidelity.tolerancePct}% tolerance.`);
1041
+ if (headline.fixFlipRate) lines.push(`- **Fix-flip rate: ${pct(headline.fixFlipRate.value)}** (${headline.fixFlipRate.numerator}/${headline.fixFlipRate.denominator} arm-B-executed cases where the generated fix made the failure vanish).`);
1042
+ if (headline.fixFlipRateNonzeroRc) lines.push(`- Fix-flip rate on recorded-rc≠0 cases: ${pct(headline.fixFlipRateNonzeroRc.value)} (${headline.fixFlipRateNonzeroRc.numerator}/${headline.fixFlipRateNonzeroRc.denominator}; real recorded failures — a gold step recorded with rc 0 flips vacuously).`);
1043
+ if (headline.fixFlipAttempt1) lines.push(`- Fix-flip@1: ${pct(headline.fixFlipAttempt1.value)} (${headline.fixFlipAttempt1.numerator}/${headline.fixFlipAttempt1.denominator} cases whose attempt 1 executed — the one-shot-comparable number).`);
1044
+ if (headline.flipsByAttempt && Object.keys(headline.flipsByAttempt).length > 0) {
1045
+ const parts = Object.entries(headline.flipsByAttempt).sort((a, b) => Number(a[0]) - Number(b[0])).map(([attempt, count]) => `attempt ${attempt}: ${count}`);
1046
+ lines.push(`- Flips by attempt: ${parts.join(", ")}.`);
1578
1047
  }
1579
- const excludedByReason = {};
1580
- for (const excluded of enumeration.excluded) excludedByReason[excluded.reason] = (excludedByReason[excluded.reason] ?? 0) + 1;
1581
- const submitGoldsByCorpus = {};
1582
- const submitEntry = (corpus) => submitGoldsByCorpus[corpus] ??= {
1583
- submitOnlyCases: 0,
1584
- goldsSkippedWithinReplayable: 0
1585
- };
1586
- for (const excluded of enumeration.excluded) if (excluded.reason === "gold-only-submit-step") submitEntry(excluded.corpus).submitOnlyCases += 1;
1587
- for (const replayCase of enumeration.replayable) if (replayCase.submitGoldsSkipped > 0) submitEntry(replayCase.corpus).goldsSkippedWithinReplayable += replayCase.submitGoldsSkipped;
1588
- const report = {
1589
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1590
- corpora: options.corpora.map((c) => ({
1591
- name: c.name,
1592
- labelsPath: c.labelsPath,
1593
- preparedDir: c.preparedDir
1594
- })),
1595
- totals: {
1596
- labelEntries: enumeration.labelEntryCount,
1597
- replayable: enumeration.replayable.length,
1598
- executed: selected.length,
1599
- excludedByReason,
1600
- submitGoldsByCorpus
1601
- },
1602
- headline: {
1603
- replayabilityRate: {
1604
- numerator: replayed.length,
1605
- denominator: selected.length,
1606
- value: rate(replayed.length, selected.length)
1607
- },
1608
- signatureStrictRate: {
1609
- numerator: signatureStrict.length,
1610
- denominator: selected.length,
1611
- value: rate(signatureStrict.length, selected.length)
1612
- },
1613
- prefixFidelity,
1614
- fixFlipRate: options.fix !== "none" ? {
1615
- numerator: flipped.length,
1616
- denominator: armBExecuted.length,
1617
- value: rate(flipped.length, armBExecuted.length)
1618
- } : null,
1619
- fixFlipRateNonzeroRc: options.fix !== "none" ? {
1620
- numerator: flippedNonzeroRc.length,
1621
- denominator: armBNonzeroRc.length,
1622
- value: rate(flippedNonzeroRc.length, armBNonzeroRc.length)
1623
- } : null,
1624
- fixFlipAttempt1: options.fix === "loop" ? {
1625
- numerator: flippedAt1.length,
1626
- denominator: attempt1Executed.length,
1627
- value: rate(flippedAt1.length, attempt1Executed.length)
1628
- } : null,
1629
- flipsByAttempt: options.fix === "loop" ? flipsByAttempt : null
1630
- },
1631
- llm,
1632
- excluded: enumeration.excluded,
1633
- pullFailures,
1634
- cases: rows
1635
- };
1636
- writeFileSync(join(options.out, "batch-report.json"), `${JSON.stringify(report, null, 2)}\n`);
1637
- writeFileSync(join(options.out, "batch-report.md"), renderBatchReport(report));
1638
- return report;
1639
- }
1640
- function pct(value) {
1641
- return value === null ? "—" : `${(value * 100).toFixed(1)}%`;
1642
1048
  }
1643
- function renderBatchReport(report) {
1644
- const lines = [];
1645
- const { headline, totals } = report;
1646
- lines.push("# Replay-verify batch report");
1647
- lines.push("");
1648
- lines.push(`Generated ${report.generatedAt}.`);
1649
- lines.push("");
1650
- lines.push("## Headline");
1651
- lines.push("");
1652
- const fidelity = headline.prefixFidelity;
1653
- lines.push(`- **Replayability rate: ${pct(headline.replayabilityRate.value)}** (${headline.replayabilityRate.numerator}/${headline.replayabilityRate.denominator} replayable cases where the prefix replayed within ${fidelity.tolerancePct}% divergence AND arm A reproduced an observed failure at the gold step k).`);
1654
- lines.push(`- Signature-strict rate: ${pct(headline.signatureStrictRate.value)} (${headline.signatureStrictRate.numerator}/${headline.signatureStrictRate.denominator}; additionally requires the recorded error substring in arm A output).`);
1655
- lines.push(`- **Prefix divergence: ${fidelity.divergencePct === null ? "—" : `${fidelity.divergencePct}%`}** (${fidelity.divergentSteps}/${fidelity.executedSteps} executed prefix steps did not confirm the recording: ${fidelity.returncodeMismatches} returncode mismatches, ${fidelity.unknownExpectations} with no recorded returncode to check). ${fidelity.casesWithinTolerance}/${fidelity.casesExecuted} executed cases are within the ${fidelity.tolerancePct}% tolerance.`);
1656
- if (headline.fixFlipRate) lines.push(`- **Fix-flip rate: ${pct(headline.fixFlipRate.value)}** (${headline.fixFlipRate.numerator}/${headline.fixFlipRate.denominator} arm-B-executed cases where the generated fix made the failure vanish).`);
1657
- if (headline.fixFlipRateNonzeroRc) lines.push(`- Fix-flip rate on recorded-rc≠0 cases: ${pct(headline.fixFlipRateNonzeroRc.value)} (${headline.fixFlipRateNonzeroRc.numerator}/${headline.fixFlipRateNonzeroRc.denominator}; real recorded failures — a gold step recorded with rc 0 flips vacuously).`);
1658
- if (headline.fixFlipAttempt1) lines.push(`- Fix-flip@1: ${pct(headline.fixFlipAttempt1.value)} (${headline.fixFlipAttempt1.numerator}/${headline.fixFlipAttempt1.denominator} cases whose attempt 1 executed — the one-shot-comparable number).`);
1659
- if (headline.flipsByAttempt && Object.keys(headline.flipsByAttempt).length > 0) {
1660
- const parts = Object.entries(headline.flipsByAttempt).sort((a, b) => Number(a[0]) - Number(b[0])).map(([attempt, count]) => `attempt ${attempt}: ${count}`);
1661
- lines.push(`- Flips by attempt: ${parts.join(", ")}.`);
1662
- }
1049
+ /** How many label entries were replayable, executed and excluded, and why. */
1050
+ function pushEnumeration(report, lines) {
1051
+ const { totals } = report;
1663
1052
  lines.push("");
1664
1053
  lines.push("## Enumeration");
1665
1054
  lines.push("");
@@ -1677,6 +1066,9 @@ function renderBatchReport(report) {
1677
1066
  lines.push("| --- | --- | --- |");
1678
1067
  for (const [corpus, stats] of submitEntries.sort((a, b) => a[0].localeCompare(b[0]))) lines.push(`| ${corpus} | ${stats.submitOnlyCases} | ${stats.goldsSkippedWithinReplayable} |`);
1679
1068
  }
1069
+ }
1070
+ /** The images that could not be pulled or built. */
1071
+ function pushPullFailures(report, lines) {
1680
1072
  if (report.pullFailures.length > 0) {
1681
1073
  lines.push("");
1682
1074
  lines.push("## Image pull/build failures");
@@ -1685,6 +1077,9 @@ function renderBatchReport(report) {
1685
1077
  lines.push("| --- | --- | --- | --- |");
1686
1078
  for (const failure of report.pullFailures) lines.push(`| ${failure.corpus} | ${failure.trajId} | \`${failure.image}\` | ${failure.error.replaceAll("|", "\\|")} |`);
1687
1079
  }
1080
+ }
1081
+ /** One row per replayed case. */
1082
+ function pushCaseResults(report, lines) {
1688
1083
  lines.push("");
1689
1084
  lines.push("## Per-case results");
1690
1085
  lines.push("");
@@ -1715,16 +1110,652 @@ function renderBatchReport(report) {
1715
1110
  (row.wallMs / 1e3).toFixed(1)
1716
1111
  ].join(" | "));
1717
1112
  }
1113
+ }
1114
+ /** The model, calls and tokens spent generating fixes. */
1115
+ function pushLlmUsage(report, lines) {
1718
1116
  if (report.llm) {
1719
1117
  lines.push("");
1720
1118
  lines.push("## LLM fix generation");
1721
1119
  lines.push("");
1722
1120
  lines.push(`Model ${report.llm.model}: ${report.llm.calls} calls (${report.llm.failures} failed), ${report.llm.promptTokens} prompt + ${report.llm.completionTokens} completion tokens.`);
1723
1121
  }
1122
+ }
1123
+ function renderBatchReport(report) {
1124
+ const lines = [];
1125
+ pushHeadline(report, lines);
1126
+ pushEnumeration(report, lines);
1127
+ pushPullFailures(report, lines);
1128
+ pushCaseResults(report, lines);
1129
+ pushLlmUsage(report, lines);
1724
1130
  lines.push("");
1725
1131
  return lines.join("\n");
1726
1132
  }
1727
1133
  //#endregion
1134
+ //#region src/trajectory-replay/corpus.ts
1135
+ /**
1136
+ * Corpus enumeration for batch replay verification.
1137
+ *
1138
+ * A labeled trajectory corpus is a gold label file (array of trajectory
1139
+ * entries with `incorrect_stages[].incorrect_step_ids`) plus a prepared
1140
+ * directory:
1141
+ * <prepared>/normalized/<traj_id>/steps.json normalized steps
1142
+ * <prepared>/normalized/<traj_id>/task.md optional task statement
1143
+ * <prepared>/extracted/<traj_id>/swe_raw/** raw mini-SWE trajectory
1144
+ *
1145
+ * Only trajectories that record their environment are replayable: the raw
1146
+ * trajectory must carry `info.docker_config.base_image`. Every excluded case
1147
+ * is returned with a machine-readable reason — the batch report surfaces the
1148
+ * full exclusion table, never a silently shrunk denominator.
1149
+ */
1150
+ function readLabelEntries(labelsPath) {
1151
+ const parsed = JSON.parse(readFileSync(labelsPath, "utf8"));
1152
+ if (!Array.isArray(parsed)) throw new Error(`${labelsPath} is not a JSON array of label entries`);
1153
+ for (const entry of parsed) if (typeof entry.traj_id !== "string") throw new Error(`${labelsPath} has an entry without a string traj_id`);
1154
+ return parsed;
1155
+ }
1156
+ function goldIncorrectSteps(entry) {
1157
+ const ids = /* @__PURE__ */ new Set();
1158
+ for (const stage of entry.incorrect_stages ?? []) for (const id of stage.incorrect_step_ids ?? []) ids.add(id);
1159
+ return [...ids].sort((a, b) => a - b);
1160
+ }
1161
+ function findRawTrajFiles(sweRawDir) {
1162
+ if (!existsSync(sweRawDir)) return [];
1163
+ return readdirSync(sweRawDir, {
1164
+ recursive: true,
1165
+ encoding: "utf8"
1166
+ }).filter((relative) => relative.endsWith(".traj.json")).map((relative) => join(sweRawDir, relative)).sort();
1167
+ }
1168
+ /** First non-empty output line of the trajectory's first bare `pwd` step. */
1169
+ function cwdFromPwdObservation(steps) {
1170
+ for (const step of steps) {
1171
+ if (step.action.trim() !== "pwd") continue;
1172
+ const line = parseObservationOutput(step.observation).split("\n").map((l) => l.trim()).find((l) => l.startsWith("/"));
1173
+ if (line) return line;
1174
+ }
1175
+ return null;
1176
+ }
1177
+ function taskStatementOf(preparedDir, trajId, raw) {
1178
+ const taskPath = join(preparedDir, "normalized", trajId, "task.md");
1179
+ if (existsSync(taskPath)) {
1180
+ const text = readFileSync(taskPath, "utf8").trim();
1181
+ if (text.length > 0) return text;
1182
+ }
1183
+ const userMessage = raw.messages?.find((m) => m.role === "user");
1184
+ if (typeof userMessage?.content === "string" && userMessage.content.trim().length > 0) return userMessage.content.trim();
1185
+ return null;
1186
+ }
1187
+ /**
1188
+ * Resolves the replay resources for one trajectory, independent of gold
1189
+ * labels — the finding wire uses this with a finding-supplied step instead.
1190
+ */
1191
+ function resolveCaseResources(corpus, trajId) {
1192
+ const sweRawDir = join(corpus.preparedDir, "extracted", trajId, "swe_raw");
1193
+ const rawFiles = findRawTrajFiles(sweRawDir);
1194
+ if (rawFiles.length === 0) return {
1195
+ resolved: false,
1196
+ reason: "no-swe-raw-trajectory",
1197
+ detail: sweRawDir
1198
+ };
1199
+ if (rawFiles.length > 1) return {
1200
+ resolved: false,
1201
+ reason: "ambiguous-swe-raw-trajectory",
1202
+ detail: rawFiles.join(", ")
1203
+ };
1204
+ let raw;
1205
+ try {
1206
+ raw = JSON.parse(readFileSync(rawFiles[0], "utf8"));
1207
+ } catch (err) {
1208
+ return {
1209
+ resolved: false,
1210
+ reason: "unreadable-raw-trajectory",
1211
+ detail: `${rawFiles[0]}: ${err instanceof Error ? err.message : String(err)}`
1212
+ };
1213
+ }
1214
+ const dockerConfig = raw.info?.docker_config;
1215
+ const image = dockerConfig?.base_image;
1216
+ if (typeof image !== "string" || image.length === 0) return {
1217
+ resolved: false,
1218
+ reason: "no-docker-image",
1219
+ detail: rawFiles[0]
1220
+ };
1221
+ const stepsPath = join(corpus.preparedDir, "normalized", trajId, "steps.json");
1222
+ if (!existsSync(stepsPath)) return {
1223
+ resolved: false,
1224
+ reason: "missing-steps-json",
1225
+ detail: stepsPath
1226
+ };
1227
+ const steps = JSON.parse(readFileSync(stepsPath, "utf8"));
1228
+ const runConfigCwd = raw.info?.config?.environment?.cwd;
1229
+ const dockerCwd = dockerConfig?.cwd;
1230
+ let cwd;
1231
+ let cwdSource;
1232
+ if (typeof runConfigCwd === "string" && runConfigCwd.length > 0) {
1233
+ cwd = runConfigCwd;
1234
+ cwdSource = "run-config";
1235
+ } else if (typeof dockerCwd === "string" && dockerCwd.length > 0) {
1236
+ cwd = dockerCwd;
1237
+ cwdSource = "docker-config";
1238
+ } else {
1239
+ const observed = cwdFromPwdObservation(steps);
1240
+ if (!observed) return {
1241
+ resolved: false,
1242
+ reason: "cwd-underivable"
1243
+ };
1244
+ cwd = observed;
1245
+ cwdSource = "pwd-observation";
1246
+ }
1247
+ const recordedTimeout = raw.info?.config?.environment?.timeout;
1248
+ return {
1249
+ resolved: true,
1250
+ resources: {
1251
+ corpus: corpus.name,
1252
+ trajId,
1253
+ stepsPath,
1254
+ steps,
1255
+ taskStatement: taskStatementOf(corpus.preparedDir, trajId, raw),
1256
+ image,
1257
+ cwd,
1258
+ cwdSource,
1259
+ recordedStepTimeoutMs: typeof recordedTimeout === "number" && recordedTimeout > 0 ? recordedTimeout * 1e3 : null
1260
+ }
1261
+ };
1262
+ }
1263
+ /**
1264
+ * Replayable = a raw trajectory with a recorded image AND at least one gold
1265
+ * incorrect step that is a real mid-trajectory action. Gold steps whose action
1266
+ * is the submit command are skipped when choosing k (counted per case); a case
1267
+ * whose golds are ALL submit steps is excluded as gold-only-submit-step.
1268
+ * Exclusion reasons are reported in resolution order:
1269
+ * raw trajectory → image → gold labels → steps.json → k range → cwd.
1270
+ */
1271
+ function enumerateReplayableCases(corpora) {
1272
+ const replayable = [];
1273
+ const excluded = [];
1274
+ let labelEntryCount = 0;
1275
+ for (const corpus of corpora) for (const entry of readLabelEntries(corpus.labelsPath)) {
1276
+ labelEntryCount += 1;
1277
+ const trajId = entry.traj_id;
1278
+ const resolution = resolveCaseResources(corpus, trajId);
1279
+ if (!resolution.resolved) {
1280
+ excluded.push({
1281
+ corpus: corpus.name,
1282
+ trajId,
1283
+ reason: resolution.reason,
1284
+ detail: resolution.detail
1285
+ });
1286
+ continue;
1287
+ }
1288
+ const gold = goldIncorrectSteps(entry);
1289
+ if (gold.length === 0) {
1290
+ excluded.push({
1291
+ corpus: corpus.name,
1292
+ trajId,
1293
+ reason: "no-gold-incorrect-step"
1294
+ });
1295
+ continue;
1296
+ }
1297
+ const resources = resolution.resources;
1298
+ let submitGoldsSkipped = 0;
1299
+ let target = null;
1300
+ let missingGoldId = null;
1301
+ for (const goldId of gold) {
1302
+ const step = resources.steps.find((s) => s.step_id === goldId);
1303
+ if (!step) {
1304
+ missingGoldId = goldId;
1305
+ break;
1306
+ }
1307
+ if (step.action.trim() === "submit" || isSubmitOnlyAction(step.action)) {
1308
+ submitGoldsSkipped += 1;
1309
+ continue;
1310
+ }
1311
+ target = step;
1312
+ break;
1313
+ }
1314
+ if (missingGoldId !== null) {
1315
+ excluded.push({
1316
+ corpus: corpus.name,
1317
+ trajId,
1318
+ reason: "gold-step-outside-steps",
1319
+ detail: `k=${missingGoldId}, steps=${resources.steps.length}`
1320
+ });
1321
+ continue;
1322
+ }
1323
+ if (!target) {
1324
+ excluded.push({
1325
+ corpus: corpus.name,
1326
+ trajId,
1327
+ reason: "gold-only-submit-step",
1328
+ detail: `${submitGoldsSkipped} gold step(s), all submit commands`
1329
+ });
1330
+ continue;
1331
+ }
1332
+ const recordedReturncodeAtK = parseRecordedReturncode(target.observation);
1333
+ if (recordedReturncodeAtK === null) {
1334
+ excluded.push({
1335
+ corpus: corpus.name,
1336
+ trajId,
1337
+ reason: "no-recorded-returncode-at-k",
1338
+ detail: `k=${target.step_id}; arm A cannot confirm execution without a recorded returncode`
1339
+ });
1340
+ continue;
1341
+ }
1342
+ replayable.push({
1343
+ ...resources,
1344
+ goldIncorrectSteps: gold,
1345
+ k: target.step_id,
1346
+ submitGoldsSkipped,
1347
+ recordedReturncodeAtK
1348
+ });
1349
+ }
1350
+ return {
1351
+ replayable,
1352
+ excluded,
1353
+ labelEntryCount
1354
+ };
1355
+ }
1356
+ //#endregion
1357
+ //#region src/trajectory-replay/image-preparer.ts
1358
+ /**
1359
+ * Replay-ready image derivation.
1360
+ *
1361
+ * A recorded trajectory names the image it ran in, but the image is not always
1362
+ * runnable as-is: sandbox platforms pin customer commands to a non-root
1363
+ * identity, so a root-owned working tree must be chowned before the replay can
1364
+ * write to it. `ImagePreparer` is that step, injectable so a consumer whose
1365
+ * images are already replay-ready supplies its own no-op or none at all.
1366
+ */
1367
+ const execFileAsync = promisify(execFile);
1368
+ function derivedImageTag(image, cwd) {
1369
+ return `ctb-replay:${createHash("sha256").update(`${image}\n${cwd}`).digest("hex").slice(0, 12)}-uid1000`;
1370
+ }
1371
+ /**
1372
+ * Pulls the base image when absent and builds `FROM <base>; RUN chown -R
1373
+ * 1000:1000 <cwd>` tagged by content hash, so repeated batches reuse both
1374
+ * the pull and the build. cwd `/` skips the chown (never chown -R /) and
1375
+ * replays on the base image directly.
1376
+ */
1377
+ function dockerImagePreparer(options = {}) {
1378
+ const pullTimeoutMs = options.pullTimeoutMs ?? 12e5;
1379
+ const buildTimeoutMs = options.buildTimeoutMs ?? 9e5;
1380
+ const imageExists = async (tag) => {
1381
+ try {
1382
+ await execFileAsync("docker", [
1383
+ "image",
1384
+ "inspect",
1385
+ tag
1386
+ ], { maxBuffer: 8388608 });
1387
+ return true;
1388
+ } catch {
1389
+ return false;
1390
+ }
1391
+ };
1392
+ const errorTail = (err) => {
1393
+ return (err && typeof err === "object" && "stderr" in err && typeof err.stderr === "string" && err.stderr.length > 0 ? err.stderr : err instanceof Error ? err.message : String(err)).trim().split("\n").slice(-3).join(" | ").slice(0, 400);
1394
+ };
1395
+ return { async ensure(image, cwd) {
1396
+ if (cwd === "/") {
1397
+ if (await imageExists(image)) return {
1398
+ succeeded: true,
1399
+ value: {
1400
+ derivedImage: image,
1401
+ pulled: false,
1402
+ built: false
1403
+ }
1404
+ };
1405
+ try {
1406
+ await execFileAsync("docker", ["pull", image], {
1407
+ timeout: pullTimeoutMs,
1408
+ maxBuffer: 33554432
1409
+ });
1410
+ } catch (err) {
1411
+ return {
1412
+ succeeded: false,
1413
+ error: `pull ${image}: ${errorTail(err)}`
1414
+ };
1415
+ }
1416
+ return {
1417
+ succeeded: true,
1418
+ value: {
1419
+ derivedImage: image,
1420
+ pulled: true,
1421
+ built: false
1422
+ }
1423
+ };
1424
+ }
1425
+ const derived = derivedImageTag(image, cwd);
1426
+ if (await imageExists(derived)) return {
1427
+ succeeded: true,
1428
+ value: {
1429
+ derivedImage: derived,
1430
+ pulled: false,
1431
+ built: false
1432
+ }
1433
+ };
1434
+ let pulled = false;
1435
+ if (!await imageExists(image)) try {
1436
+ await execFileAsync("docker", ["pull", image], {
1437
+ timeout: pullTimeoutMs,
1438
+ maxBuffer: 33554432
1439
+ });
1440
+ pulled = true;
1441
+ } catch (err) {
1442
+ return {
1443
+ succeeded: false,
1444
+ error: `pull ${image}: ${errorTail(err)}`
1445
+ };
1446
+ }
1447
+ const contextDir = mkdtempSync(join(tmpdir(), "ctb-replay-image-"));
1448
+ const quotedCwd = `'${cwd.replaceAll("'", `'\\''`)}'`;
1449
+ writeFileSync(join(contextDir, "Dockerfile"), `FROM ${image}\nRUN chown -R 1000:1000 ${quotedCwd}\n`);
1450
+ try {
1451
+ await execFileAsync("docker", [
1452
+ "build",
1453
+ "-t",
1454
+ derived,
1455
+ contextDir
1456
+ ], {
1457
+ timeout: buildTimeoutMs,
1458
+ maxBuffer: 33554432
1459
+ });
1460
+ } catch (err) {
1461
+ return {
1462
+ succeeded: false,
1463
+ error: `build ${derived} from ${image}: ${errorTail(err)}`
1464
+ };
1465
+ }
1466
+ return {
1467
+ succeeded: true,
1468
+ value: {
1469
+ derivedImage: derived,
1470
+ pulled,
1471
+ built: true
1472
+ }
1473
+ };
1474
+ } };
1475
+ }
1476
+ //#endregion
1477
+ //#region src/trajectory-replay/batch.ts
1478
+ /**
1479
+ * Batch replay verification across gold-labeled trajectory corpora.
1480
+ *
1481
+ * For every replayable case (a raw trajectory with a recorded image AND at
1482
+ * least one gold incorrect step) the batch:
1483
+ * 1. derives the replay-ready image through the injected `ImagePreparer`,
1484
+ * 2. replays the prefix and runs arm A — the recorded gold step k, and
1485
+ * 3. optionally generates a corrected command with one LLM call and runs
1486
+ * arm B in its own fresh session.
1487
+ *
1488
+ * Headline metrics:
1489
+ * replayability rate — fraction of replayable cases where the prefix
1490
+ * replays within the divergence tolerance AND arm A reproduces a
1491
+ * recorded failure at k; a zero exit without an error signature is not
1492
+ * evidence of failure;
1493
+ * prefix fidelity — executed prefix steps and the share of them that did
1494
+ * not confirm the recording, split by kind. A corpus whose recordings
1495
+ * carry no returncodes shows up here as unknown-expectation steps, never
1496
+ * as a clean replay;
1497
+ * fix-flip rate — fraction of arm-B-executed cases where the failure
1498
+ * vanished (exit 0, signature absent).
1499
+ *
1500
+ * Image pulls and execs run strictly serially: pulls contend on disk and
1501
+ * registry bandwidth, and serial cases keep wall-time attribution per case
1502
+ * honest. Pull failures are report rows, never silent skips.
1503
+ */
1504
+ function rate(numerator, denominator) {
1505
+ return denominator > 0 ? numerator / denominator : null;
1506
+ }
1507
+ function caseIdentity(replayCase) {
1508
+ return {
1509
+ corpus: replayCase.corpus,
1510
+ trajId: replayCase.trajId,
1511
+ image: replayCase.image,
1512
+ cwd: replayCase.cwd,
1513
+ cwdSource: replayCase.cwdSource,
1514
+ k: replayCase.k,
1515
+ stepCount: replayCase.steps.length,
1516
+ goldIncorrectSteps: replayCase.goldIncorrectSteps,
1517
+ submitGoldsSkipped: replayCase.submitGoldsSkipped,
1518
+ recordedReturncodeAtK: replayCase.recordedReturncodeAtK
1519
+ };
1520
+ }
1521
+ /** The row of a case that never produced an arm A verdict. */
1522
+ function unreplayedRow(replayCase, caseStart, failure) {
1523
+ return {
1524
+ ...caseIdentity(replayCase),
1525
+ derivedImage: failure.derivedImage,
1526
+ signature: null,
1527
+ status: failure.status,
1528
+ error: failure.error,
1529
+ imagePulled: failure.imagePulled,
1530
+ imageBuilt: failure.imageBuilt,
1531
+ prefixExecuted: null,
1532
+ prefixDivergences: null,
1533
+ prefixDivergencePct: null,
1534
+ prefixConfirmed: null,
1535
+ prefixReturncodeMismatches: null,
1536
+ prefixUnknownExpectations: null,
1537
+ armAExit: null,
1538
+ armAReturncodeMatch: false,
1539
+ armASignatureMatch: false,
1540
+ replayed: false,
1541
+ fix: null,
1542
+ wallMs: Date.now() - caseStart
1543
+ };
1544
+ }
1545
+ function verdictRow(replayCase, caseStart, image, verdict, returncodeMatch) {
1546
+ return {
1547
+ ...caseIdentity(replayCase),
1548
+ derivedImage: image.derivedImage,
1549
+ signature: verdict.signature,
1550
+ status: "ok",
1551
+ error: null,
1552
+ imagePulled: image.pulled,
1553
+ imageBuilt: image.built,
1554
+ prefixExecuted: verdict.prefixExecuted,
1555
+ prefixDivergences: verdict.prefixDivergences.length,
1556
+ prefixDivergencePct: verdict.prefixDivergencePct,
1557
+ prefixConfirmed: verdict.prefixConfirmed,
1558
+ prefixReturncodeMismatches: verdict.prefixReturncodeMismatches,
1559
+ prefixUnknownExpectations: verdict.prefixUnknownExpectations,
1560
+ armAExit: verdict.armA.exitCode,
1561
+ armAReturncodeMatch: returncodeMatch,
1562
+ armASignatureMatch: verdict.armA.failureSignatureMatch,
1563
+ replayed: verdict.valid,
1564
+ fix: null,
1565
+ wallMs: Date.now() - caseStart
1566
+ };
1567
+ }
1568
+ /** Phase 1 for one case: prepare its image and run arm A. Every outcome is a row, never a skip. */
1569
+ async function replayArmA(run, replayCase, label, verdicts, pullFailures) {
1570
+ const { options, onProgress } = run;
1571
+ const caseStart = Date.now();
1572
+ onProgress(`${label}: preparing image ${replayCase.image}`);
1573
+ const preparation = await run.preparer.ensure(replayCase.image, replayCase.cwd);
1574
+ if (!preparation.succeeded) {
1575
+ pullFailures.push({
1576
+ corpus: replayCase.corpus,
1577
+ trajId: replayCase.trajId,
1578
+ image: replayCase.image,
1579
+ error: preparation.error
1580
+ });
1581
+ const row = unreplayedRow(replayCase, caseStart, {
1582
+ derivedImage: null,
1583
+ status: "image-unavailable",
1584
+ error: preparation.error,
1585
+ imagePulled: false,
1586
+ imageBuilt: false
1587
+ });
1588
+ appendFileSync(run.progressPath, `${JSON.stringify(row)}\n`);
1589
+ onProgress(`${label}: image unavailable — ${preparation.error}`);
1590
+ return row;
1591
+ }
1592
+ const { derivedImage, pulled, built } = preparation.value;
1593
+ const stepTimeoutMs = options.stepTimeoutMs ?? replayCase.recordedStepTimeoutMs ?? 12e4;
1594
+ const caseOut = join(options.out, caseOutDirName(replayCase));
1595
+ onProgress(`${label}: arm A on ${derivedImage} (k=${replayCase.k}, timeout ${stepTimeoutMs}ms)`);
1596
+ try {
1597
+ const verdict = await replayVerify({
1598
+ stepsPath: replayCase.stepsPath,
1599
+ image: derivedImage,
1600
+ at: replayCase.k,
1601
+ cwd: replayCase.cwd,
1602
+ out: caseOut,
1603
+ caseId: replayCase.trajId,
1604
+ stepTimeoutMs,
1605
+ prefixLimit: options.prefixLimit,
1606
+ backend: options.backendFactory(derivedImage),
1607
+ onProgress: (message) => onProgress(`${label}: ${message}`)
1608
+ });
1609
+ verdicts.set(replayCase.trajId, verdict);
1610
+ const returncodeMatch = verdict.recordedReturncode !== null && verdict.armA.exitCode === verdict.recordedReturncode;
1611
+ const row = verdictRow(replayCase, caseStart, {
1612
+ derivedImage,
1613
+ pulled,
1614
+ built
1615
+ }, verdict, returncodeMatch);
1616
+ appendFileSync(run.progressPath, `${JSON.stringify(row)}\n`);
1617
+ onProgress(`${label}: armA exit=${verdict.armA.exitCode} rcMatch=${returncodeMatch} divergences=${verdict.prefixDivergences.length}/${verdict.prefixExecuted} (${verdict.prefixDivergencePct}%, ${verdict.prefixUnknownExpectations} unknown-expectation)`);
1618
+ return row;
1619
+ } catch (err) {
1620
+ const message = err instanceof Error ? err.message : String(err);
1621
+ const row = unreplayedRow(replayCase, caseStart, {
1622
+ derivedImage,
1623
+ status: "replay-error",
1624
+ error: message.slice(0, 500),
1625
+ imagePulled: pulled,
1626
+ imageBuilt: built
1627
+ });
1628
+ appendFileSync(run.progressPath, `${JSON.stringify(row)}\n`);
1629
+ onProgress(`${label}: replay error — ${message.slice(0, 200)}`);
1630
+ return row;
1631
+ }
1632
+ }
1633
+ function prefixFidelityOf(executedRows) {
1634
+ const sumOver = (pick) => executedRows.reduce((total, row) => total + (pick(row) ?? 0), 0);
1635
+ const executedSteps = sumOver((r) => r.prefixExecuted);
1636
+ const divergentSteps = sumOver((r) => r.prefixDivergences);
1637
+ return {
1638
+ executedSteps,
1639
+ divergentSteps,
1640
+ returncodeMismatches: sumOver((r) => r.prefixReturncodeMismatches),
1641
+ unknownExpectations: sumOver((r) => r.prefixUnknownExpectations),
1642
+ divergencePct: executedSteps > 0 ? Number((divergentSteps / executedSteps * 100).toFixed(1)) : null,
1643
+ tolerancePct: 10,
1644
+ casesWithinTolerance: executedRows.filter((r) => r.prefixDivergencePct !== null && r.prefixDivergencePct <= 10).length,
1645
+ casesExecuted: executedRows.length
1646
+ };
1647
+ }
1648
+ /** Fix-flip headline rates over the cases whose arm B executed. */
1649
+ function fixFlipHeadline(rows, fix) {
1650
+ const armBExecuted = rows.filter((r) => r.fix?.attempted && r.fix.failureVanished !== null);
1651
+ const flipped = armBExecuted.filter((r) => r.fix.failureVanished === true);
1652
+ const armBNonzeroRc = armBExecuted.filter((r) => r.recordedReturncodeAtK !== null && r.recordedReturncodeAtK !== 0);
1653
+ const flippedNonzeroRc = armBNonzeroRc.filter((r) => r.fix.failureVanished === true);
1654
+ const attempt1Executed = rows.filter((r) => r.fix?.attempts?.find((a) => a.attempt === 1)?.executed === true);
1655
+ const flippedAt1 = rows.filter((r) => r.fix?.flippedAtAttempt === 1);
1656
+ const flipsByAttempt = {};
1657
+ for (const row of rows) {
1658
+ const at = row.fix?.flippedAtAttempt;
1659
+ if (typeof at === "number") flipsByAttempt[String(at)] = (flipsByAttempt[String(at)] ?? 0) + 1;
1660
+ }
1661
+ return {
1662
+ fixFlipRate: fix !== "none" ? {
1663
+ numerator: flipped.length,
1664
+ denominator: armBExecuted.length,
1665
+ value: rate(flipped.length, armBExecuted.length)
1666
+ } : null,
1667
+ fixFlipRateNonzeroRc: fix !== "none" ? {
1668
+ numerator: flippedNonzeroRc.length,
1669
+ denominator: armBNonzeroRc.length,
1670
+ value: rate(flippedNonzeroRc.length, armBNonzeroRc.length)
1671
+ } : null,
1672
+ fixFlipAttempt1: fix === "loop" ? {
1673
+ numerator: flippedAt1.length,
1674
+ denominator: attempt1Executed.length,
1675
+ value: rate(flippedAt1.length, attempt1Executed.length)
1676
+ } : null,
1677
+ flipsByAttempt: fix === "loop" ? flipsByAttempt : null
1678
+ };
1679
+ }
1680
+ /** Per-corpus submit-gold accounting: cases dropped for pure-submit golds, and submit golds skipped inside replayable cases. */
1681
+ function submitGoldsOf(enumeration) {
1682
+ const byCorpus = {};
1683
+ const entry = (corpus) => byCorpus[corpus] ??= {
1684
+ submitOnlyCases: 0,
1685
+ goldsSkippedWithinReplayable: 0
1686
+ };
1687
+ for (const excluded of enumeration.excluded) if (excluded.reason === "gold-only-submit-step") entry(excluded.corpus).submitOnlyCases += 1;
1688
+ for (const replayCase of enumeration.replayable) if (replayCase.submitGoldsSkipped > 0) entry(replayCase.corpus).goldsSkippedWithinReplayable += replayCase.submitGoldsSkipped;
1689
+ return byCorpus;
1690
+ }
1691
+ function buildBatchReport(options, enumeration, selected, rows, llm, pullFailures) {
1692
+ const executedRows = rows.filter((r) => r.status === "ok");
1693
+ const replayed = executedRows.filter((r) => r.replayed);
1694
+ const signatureStrict = executedRows.filter((r) => r.replayed && r.armASignatureMatch);
1695
+ const excludedByReason = {};
1696
+ for (const excluded of enumeration.excluded) excludedByReason[excluded.reason] = (excludedByReason[excluded.reason] ?? 0) + 1;
1697
+ return {
1698
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1699
+ corpora: options.corpora.map((c) => ({
1700
+ name: c.name,
1701
+ labelsPath: c.labelsPath,
1702
+ preparedDir: c.preparedDir
1703
+ })),
1704
+ totals: {
1705
+ labelEntries: enumeration.labelEntryCount,
1706
+ replayable: enumeration.replayable.length,
1707
+ executed: selected.length,
1708
+ excludedByReason,
1709
+ submitGoldsByCorpus: submitGoldsOf(enumeration)
1710
+ },
1711
+ headline: {
1712
+ replayabilityRate: {
1713
+ numerator: replayed.length,
1714
+ denominator: selected.length,
1715
+ value: rate(replayed.length, selected.length)
1716
+ },
1717
+ signatureStrictRate: {
1718
+ numerator: signatureStrict.length,
1719
+ denominator: selected.length,
1720
+ value: rate(signatureStrict.length, selected.length)
1721
+ },
1722
+ prefixFidelity: prefixFidelityOf(executedRows),
1723
+ ...fixFlipHeadline(rows, options.fix)
1724
+ },
1725
+ llm,
1726
+ excluded: enumeration.excluded,
1727
+ pullFailures,
1728
+ cases: rows
1729
+ };
1730
+ }
1731
+ async function runReplayBatch(options) {
1732
+ const onProgress = options.onProgress ?? (() => {});
1733
+ const enumeration = enumerateReplayableCases(options.corpora);
1734
+ let selected = enumeration.replayable;
1735
+ if (options.caseFilter) selected = selected.filter((c) => c.trajId.includes(options.caseFilter));
1736
+ if (options.caseLimit !== void 0) selected = selected.slice(0, options.caseLimit);
1737
+ onProgress(`enumerated ${enumeration.labelEntryCount} label entries → ${enumeration.replayable.length} replayable, ${enumeration.excluded.length} excluded; executing ${selected.length}`);
1738
+ mkdirSync(options.out, { recursive: true });
1739
+ const run = {
1740
+ options,
1741
+ onProgress,
1742
+ progressPath: join(options.out, "cases.jsonl"),
1743
+ preparer: options.preparer ?? dockerImagePreparer()
1744
+ };
1745
+ const rows = [];
1746
+ const pullFailures = [];
1747
+ const verdictByTraj = /* @__PURE__ */ new Map();
1748
+ for (const [index, replayCase] of selected.entries()) {
1749
+ const label = `[${index + 1}/${selected.length}] ${replayCase.corpus}/${replayCase.trajId}`;
1750
+ rows.push(await replayArmA(run, replayCase, label, verdictByTraj, pullFailures));
1751
+ }
1752
+ const llm = options.fix !== "none" ? await runFixPhase(run, selected, rows, verdictByTraj) : null;
1753
+ const report = buildBatchReport(options, enumeration, selected, rows, llm, pullFailures);
1754
+ writeFileSync(join(options.out, "batch-report.json"), `${JSON.stringify(report, null, 2)}\n`);
1755
+ writeFileSync(join(options.out, "batch-report.md"), renderBatchReport(report));
1756
+ return report;
1757
+ }
1758
+ //#endregion
1728
1759
  //#region src/trajectory-replay/wire.ts
1729
1760
  /**
1730
1761
  * Finding-to-replay wire: the entry point that turns a cited incorrect-steps