@tangle-network/agent-bench 0.3.6 → 0.3.8

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 (265) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/HARNESS.md +43 -0
  3. package/dist/adapters.js +24 -24
  4. package/dist/benchmarks/_harness.d.ts +1 -1
  5. package/dist/benchmarks/_harness.js +1 -1
  6. package/dist/benchmarks/aec-bench.js +2 -2
  7. package/dist/benchmarks/agentbench.js +2 -2
  8. package/dist/benchmarks/appworld.js +2 -2
  9. package/dist/benchmarks/bfcl.js +2 -2
  10. package/dist/benchmarks/commit0.js +2 -2
  11. package/dist/benchmarks/crag.js +2 -2
  12. package/dist/benchmarks/dabstep.js +2 -2
  13. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  14. package/dist/benchmarks/finresearchbench.js +2 -2
  15. package/dist/benchmarks/humaneval.d.ts +10 -1
  16. package/dist/benchmarks/humaneval.js +5 -3
  17. package/dist/benchmarks/nomiracl.js +2 -2
  18. package/dist/benchmarks/open-rag-bench.js +2 -2
  19. package/dist/benchmarks/programbench.js +2 -2
  20. package/dist/benchmarks/ragbench.js +2 -2
  21. package/dist/benchmarks/swe-bench.js +2 -2
  22. package/dist/benchmarks/t2-ragbench.js +2 -2
  23. package/dist/benchmarks/tau-bench-shared.js +2 -2
  24. package/dist/benchmarks/tau2-bench.js +3 -3
  25. package/dist/benchmarks/tau3-banking.js +3 -3
  26. package/dist/benchmarks/terminal-bench.js +2 -2
  27. package/dist/benchmarks/toollm.js +2 -2
  28. package/dist/benchmarks/webarena-verified.js +2 -2
  29. package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
  30. package/dist/{chunk-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
  31. package/dist/chunk-5H5XV76F.js.map +1 -0
  32. package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
  33. package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
  34. package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
  35. package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
  36. package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
  37. package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
  38. package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
  39. package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
  40. package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
  41. package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
  42. package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
  43. package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
  44. package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
  45. package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
  46. package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
  47. package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
  48. package/dist/{chunk-5SBJCB6W.js → chunk-V7AEBY6U.js} +22 -22
  49. package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
  50. package/dist/chunk-WSKWVEQB.js.map +1 -0
  51. package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
  52. package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
  53. package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
  54. package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
  55. package/dist/index.js +24 -24
  56. package/package.json +6 -5
  57. package/scripts/run-package-tests.mjs +30 -8
  58. package/scripts/verify-packed-consumer.mjs +1 -1
  59. package/scripts/verify-pier-agent.mts +1 -0
  60. package/src/benchmarks/_harness.ts +20 -2
  61. package/src/benchmarks/humaneval.test.mts +122 -0
  62. package/src/benchmarks/humaneval.ts +100 -27
  63. package/src/david-attribution.mts +78 -0
  64. package/src/david-goliath.mts +149 -0
  65. package/src/hev-improve.mts +25 -6
  66. package/src/humaneval-object-ablation.mts +201 -0
  67. package/src/live-improve-campaign-mbpp.mts +641 -0
  68. package/src/live-improve-campaign.mts +500 -0
  69. package/src/mbpp-structural.mts +12 -7
  70. package/src/quant-arena/README.md +144 -0
  71. package/src/quant-arena/backtest.test.mts +135 -0
  72. package/src/quant-arena/backtest.ts +218 -0
  73. package/src/quant-arena/data.test.mts +44 -0
  74. package/src/quant-arena/data.ts +141 -0
  75. package/src/quant-arena/driver.test.mts +253 -0
  76. package/src/quant-arena/driver.ts +219 -0
  77. package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
  78. package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
  79. package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
  80. package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
  81. package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
  82. package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
  83. package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
  84. package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
  85. package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
  86. package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
  87. package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
  88. package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
  89. package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
  90. package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
  91. package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
  92. package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
  93. package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
  94. package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
  95. package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
  96. package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
  97. package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
  98. package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
  99. package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
  100. package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
  101. package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
  102. package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
  103. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
  104. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
  105. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
  106. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
  107. package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
  108. package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
  109. package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
  110. package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
  111. package/src/quant-arena/holdout-certify.mts +206 -0
  112. package/src/quant-arena/holdout-certify.test.mts +82 -0
  113. package/src/quant-arena/leak-audit.test.mts +79 -0
  114. package/src/quant-arena/leak-audit.ts +95 -0
  115. package/src/quant-arena/make-fixtures.mts +161 -0
  116. package/src/quant-arena/multiplicity.test.mts +68 -0
  117. package/src/quant-arena/multiplicity.ts +87 -0
  118. package/src/quant-arena/nautilus-certify.ts +31 -0
  119. package/src/quant-arena/oms.ts +90 -0
  120. package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
  121. package/src/quant-arena/python/pyproject.toml +8 -0
  122. package/src/quant-arena/python/uv.lock +1297 -0
  123. package/src/quant-arena/python/vbt-worker.py +192 -0
  124. package/src/quant-arena/quant-loop.mts +813 -0
  125. package/src/quant-arena/quant-loop.test.mts +75 -0
  126. package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
  127. package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
  128. package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
  129. package/src/quant-arena/types.ts +133 -0
  130. package/src/quant-arena/vbt-client.ts +321 -0
  131. package/src/quant-arena/vbt-parity.test.mts +183 -0
  132. package/src/quant-arena/windows.test.mts +45 -0
  133. package/src/quant-arena/windows.ts +54 -0
  134. package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
  135. package/src/rollout-ledger/backfill-swe-arena.test.mts +338 -0
  136. package/src/rollout-ledger/settle-capture.mts +442 -0
  137. package/src/rollout-ledger/settle-capture.test.mts +270 -0
  138. package/src/stream-observe.py +45 -0
  139. package/src/stream-observe.tpl.html +247 -0
  140. package/src/supervisor-arena.mts +816 -0
  141. package/src/swe-arena/activation.mts +228 -0
  142. package/src/swe-arena/activation.test.mts +303 -0
  143. package/src/swe-arena/analyze.ts +211 -0
  144. package/src/swe-arena/arms.ts +804 -0
  145. package/src/swe-arena/bootstrap-meta.mts +188 -0
  146. package/src/swe-arena/bootstrap-meta.test.mts +51 -0
  147. package/src/swe-arena/briefing.mts +217 -0
  148. package/src/swe-arena/briefing.test.mts +178 -0
  149. package/src/swe-arena/calibrate.ts +217 -0
  150. package/src/swe-arena/capabilities.mts +76 -0
  151. package/src/swe-arena/capabilities.test.mts +57 -0
  152. package/src/swe-arena/capacity.ts +194 -0
  153. package/src/swe-arena/cell-evidence.mts +437 -0
  154. package/src/swe-arena/cell-evidence.test.mts +248 -0
  155. package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
  156. package/src/swe-arena/diagnosis-ensemble.ts +520 -0
  157. package/src/swe-arena/execution.test.mts +1170 -0
  158. package/src/swe-arena/factory-command-container.ts +284 -0
  159. package/src/swe-arena/factory-judge-child.mts +228 -0
  160. package/src/swe-arena/factory.test.mts +643 -0
  161. package/src/swe-arena/fixtures/analyze.py +80 -0
  162. package/src/swe-arena/fixtures/excludes.txt +8 -0
  163. package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
  164. package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
  165. package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
  166. package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
  167. package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
  168. package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
  169. package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
  170. package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
  171. package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
  172. package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
  173. package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
  174. package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
  175. package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
  176. package/src/swe-arena/fixtures/holdout.json +44 -0
  177. package/src/swe-arena/fixtures/instances.json +146 -0
  178. package/src/swe-arena/fixtures/ledger.jsonl +12 -0
  179. package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
  180. package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
  181. package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
  182. package/src/swe-arena/fixtures/rematch.jsonl +3 -0
  183. package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
  184. package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
  185. package/src/swe-arena/fixtures/run-report/README.md +43 -0
  186. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
  187. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
  188. package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
  189. package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
  190. package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
  191. package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
  192. package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
  193. package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
  194. package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
  195. package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
  196. package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
  197. package/src/swe-arena/fixtures/worker-tokens.json +42 -0
  198. package/src/swe-arena/fixtures.ts +237 -0
  199. package/src/swe-arena/gepa-seat.mts +583 -0
  200. package/src/swe-arena/gepa-seat.test.mts +635 -0
  201. package/src/swe-arena/holdout-certify.mts +408 -0
  202. package/src/swe-arena/holdout-certify.test.mts +160 -0
  203. package/src/swe-arena/judge-child.mts +37 -0
  204. package/src/swe-arena/ledger-orphans.mts +77 -0
  205. package/src/swe-arena/ledger-orphans.test.mts +147 -0
  206. package/src/swe-arena/lineage-record.mts +164 -0
  207. package/src/swe-arena/lineage-record.test.mts +115 -0
  208. package/src/swe-arena/manifest.mts +293 -0
  209. package/src/swe-arena/manifest.test.mts +169 -0
  210. package/src/swe-arena/materialize.ts +142 -0
  211. package/src/swe-arena/outer-loop.mts +2795 -0
  212. package/src/swe-arena/outer-loop.test.mts +696 -0
  213. package/src/swe-arena/parity.test.mts +87 -0
  214. package/src/swe-arena/premeasured-from-cells.mts +281 -0
  215. package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
  216. package/src/swe-arena/proc.test.mts +174 -0
  217. package/src/swe-arena/proc.ts +260 -0
  218. package/src/swe-arena/profiles/default-author.profile.json +4 -0
  219. package/src/swe-arena/proposer-fanout.mts +770 -0
  220. package/src/swe-arena/proposer-fanout.test.mts +619 -0
  221. package/src/swe-arena/proposer-provenance.mts +177 -0
  222. package/src/swe-arena/proposer-provenance.test.mts +106 -0
  223. package/src/swe-arena/reconcile.ts +0 -0
  224. package/src/swe-arena/replay.mts +183 -0
  225. package/src/swe-arena/replay.test.mts +300 -0
  226. package/src/swe-arena/run-experiment.mts +727 -0
  227. package/src/swe-arena/run-report.mts +75 -0
  228. package/src/swe-arena/run-supervisor.mjs +297 -0
  229. package/src/swe-arena/run-supervisor.test.mts +500 -0
  230. package/src/swe-arena/score-split.mts +140 -0
  231. package/src/swe-arena/score-split.test.mts +123 -0
  232. package/src/swe-arena/serialized-judge.ts +414 -0
  233. package/src/swe-arena/types.ts +218 -0
  234. package/src/swe-code-improve.mts +328 -0
  235. package/src/swe-emit-patch.mts +104 -0
  236. package/src/swe-improve.mts +232 -0
  237. package/src/swe-jail.ts +2 -2
  238. package/src/swe-local-proof.mts +169 -0
  239. package/src/swe-repro-calibrate.mts +446 -0
  240. package/src/swe-stream.mts +1497 -0
  241. package/src/swe-structural.mts +245 -837
  242. package/dist/chunk-LRRD7NAG.js.map +0 -1
  243. package/dist/chunk-PPYSEKFM.js.map +0 -1
  244. /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
  245. /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
  246. /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
  247. /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
  248. /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
  249. /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
  250. /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
  251. /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
  252. /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
  253. /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
  254. /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
  255. /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
  256. /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
  257. /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
  258. /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
  259. /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
  260. /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
  261. /package/dist/{chunk-5SBJCB6W.js.map → chunk-V7AEBY6U.js.map} +0 -0
  262. /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
  263. /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
  264. /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
  265. /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Typed port of the reference `fixtures/analyze.py` statistics: discordant
3
+ * pair extraction, exact two-sided sign test, and cost rollups.
4
+ *
5
+ * The sign test is NOT re-implemented: `mcnemar` from
6
+ * `@tangle-network/agent-eval` computes the identical exact two-sided
7
+ * binomial p on discordant pairs (`min(1, 2·P(X ≤ min(b,c)))`, X ~
8
+ * Binomial(b+c, 0.5)) — the same formula as `analyze.py::sign_test`.
9
+ *
10
+ * Token accounting has TWO deliberately distinct rollups:
11
+ * - `supBrainTokens` — what analyze.py prints as "SUP total tokens":
12
+ * supervisor-journal metered spend (the supervisor "brain"), journal-true
13
+ * where captured, else the runtime's `sup_spentTokens`.
14
+ * - `supTotalTokens` — brain + worker-session tokens (worker-tokens.json).
15
+ * This is the true SUP-arm spend (1,722,390 for the 12-run set) and the
16
+ * number the 2.55x SUP/SOLO ratio refers to. analyze.py never printed it;
17
+ * its printed 0.83x ratio is brain-only.
18
+ */
19
+
20
+ import { mcnemar } from '@tangle-network/agent-eval'
21
+ import type { ReconciledInstance } from './reconcile'
22
+ import type { LedgerRow, RematchRow, WorkerTokens } from './types'
23
+
24
+ export interface PairedOutcome {
25
+ iid: string
26
+ solo: boolean
27
+ sup: boolean
28
+ }
29
+
30
+ /** Raw ledger outcomes — analyze.py semantics (strict `is True`), sorted by iid. */
31
+ export function ledgerOutcomes(ledger: LedgerRow[]): PairedOutcome[] {
32
+ return [...ledger]
33
+ .sort((a, b) => (a.iid < b.iid ? -1 : a.iid > b.iid ? 1 : 0))
34
+ .map((r) => ({ iid: r.iid, solo: r.solo_resolved === true, sup: r.sup_resolved === true }))
35
+ }
36
+
37
+ /** Reconciled (re-judged, gold-gated) outcomes, sorted by iid. */
38
+ export function reconciledOutcomes(instances: ReconciledInstance[]): PairedOutcome[] {
39
+ return [...instances]
40
+ .sort((a, b) => (a.iid < b.iid ? -1 : a.iid > b.iid ? 1 : 0))
41
+ .map((r) => ({ iid: r.iid, solo: r.solo.resolved, sup: r.sup.resolved }))
42
+ }
43
+
44
+ export interface DiscordantSplit {
45
+ /** SUP resolved, SOLO not. */
46
+ supOnly: string[]
47
+ /** SOLO resolved, SUP not. */
48
+ soloOnly: string[]
49
+ both: string[]
50
+ neither: string[]
51
+ }
52
+
53
+ export function splitPairs(outcomes: PairedOutcome[]): DiscordantSplit {
54
+ const supOnly: string[] = []
55
+ const soloOnly: string[] = []
56
+ const both: string[] = []
57
+ const neither: string[] = []
58
+ for (const o of outcomes) {
59
+ if (o.sup && !o.solo) supOnly.push(o.iid)
60
+ else if (o.solo && !o.sup) soloOnly.push(o.iid)
61
+ else if (o.solo && o.sup) both.push(o.iid)
62
+ else neither.push(o.iid)
63
+ }
64
+ return { supOnly, soloOnly, both, neither }
65
+ }
66
+
67
+ export interface SignTestResult {
68
+ n: number
69
+ /** Discordant pairs where SUP (treatment) won. */
70
+ b: number
71
+ /** Discordant pairs where SOLO (control) won. */
72
+ c: number
73
+ nDiscordant: number
74
+ /** Exact two-sided binomial sign-test p on discordant pairs. */
75
+ pValue: number
76
+ }
77
+
78
+ /** Exact paired sign test, SOLO as control and SUP as treatment. */
79
+ export function pairedSignTest(outcomes: PairedOutcome[]): SignTestResult {
80
+ const result = mcnemar(
81
+ outcomes.map((o) => o.solo),
82
+ outcomes.map((o) => o.sup),
83
+ )
84
+ const { n, b, c, nDiscordant, pValue } = result
85
+ return { n, b, c, nDiscordant, pValue }
86
+ }
87
+
88
+ export interface CostRollup {
89
+ soloTokens: number
90
+ /** analyze.py's "SUP total tokens": journal-true brain spend, else sup_spentTokens. */
91
+ supBrainTokens: number
92
+ /** Σ worker-session tokens (worker-tokens.json; instances without workers contribute 0). */
93
+ supWorkerTokens: number
94
+ /** Brain + workers — the true SUP-arm token spend. */
95
+ supTotalTokens: number
96
+ supUsd: number
97
+ /** Blended $ per token from SUP runtime accounting (analyze.py's `rate`). */
98
+ blendedRatePerTok: number
99
+ soloUsdDerived: number
100
+ /** supBrainTokens / soloTokens — the ratio analyze.py prints (0.83x). */
101
+ brainTokenRatio: number
102
+ /** supTotalTokens / soloTokens — the true-spend ratio (2.55x). */
103
+ totalTokenRatio: number
104
+ soloWallS: number
105
+ supWallS: number
106
+ wallRatio: number
107
+ /** iids where the runtime's sup_spentTokens disagrees with journal-true (no-winner zeroing). */
108
+ telemetryGaps: string[]
109
+ }
110
+
111
+ /**
112
+ * Port of analyze.py's cost section over the full ledger (all 12 paired runs
113
+ * — cost is a property of the runs, not of the gradeable subset), extended
114
+ * with the worker-token rollup.
115
+ */
116
+ export function costRollup(
117
+ ledger: LedgerRow[],
118
+ supJournalTrue: Record<string, number | null>,
119
+ workerTokens: Record<string, WorkerTokens>,
120
+ ): CostRollup {
121
+ const rows = [...ledger].sort((a, b) => (a.iid < b.iid ? -1 : a.iid > b.iid ? 1 : 0))
122
+ let soloTokens = 0
123
+ let supBrainTokens = 0
124
+ let supUsd = 0
125
+ let soloWallS = 0
126
+ let supWallS = 0
127
+ const telemetryGaps: string[] = []
128
+ for (const r of rows) {
129
+ soloTokens += r.solo_tokens
130
+ const journalTrue = supJournalTrue[r.iid] ?? null
131
+ supBrainTokens += journalTrue ?? r.sup_spentTokens ?? 0
132
+ supUsd += r.sup_spentUsd ?? 0
133
+ soloWallS += r.solo_wall_s
134
+ supWallS += r.sup_wall_s
135
+ if (journalTrue !== null && (r.sup_spentTokens ?? 0) !== journalTrue) telemetryGaps.push(r.iid)
136
+ }
137
+ const supWorkerTokens = Object.values(workerTokens).reduce((s, w) => s + w.worker_tok, 0)
138
+ const supTotalTokens = supBrainTokens + supWorkerTokens
139
+ const blendedRatePerTok = supBrainTokens > 0 ? supUsd / supBrainTokens : 0
140
+ return {
141
+ soloTokens,
142
+ supBrainTokens,
143
+ supWorkerTokens,
144
+ supTotalTokens,
145
+ supUsd,
146
+ blendedRatePerTok,
147
+ soloUsdDerived: soloTokens * blendedRatePerTok,
148
+ brainTokenRatio: soloTokens > 0 ? supBrainTokens / soloTokens : 0,
149
+ totalTokenRatio: soloTokens > 0 ? supTotalTokens / soloTokens : 0,
150
+ soloWallS,
151
+ supWallS,
152
+ wallRatio: soloWallS > 0 ? supWallS / soloWallS : 0,
153
+ telemetryGaps,
154
+ }
155
+ }
156
+
157
+ /** Round label for the original head-to-head SUP run in the ledger. */
158
+ export type SupRound = 'SUP' | 'SUP2' | 'SUP3' | 'SUP4'
159
+
160
+ export interface RoundState {
161
+ round: SupRound
162
+ resolved: boolean
163
+ verifyPass: boolean
164
+ patchLines: number
165
+ verdict: 'delivered' | 'no-winner' | 'best-effort' | null
166
+ delivered: boolean | null
167
+ spentTokens: number | null
168
+ }
169
+
170
+ /**
171
+ * Supervisor evolution progression: the original SUP run (round 0, from the
172
+ * ledger) followed by each rematch round, for every instance that appears in
173
+ * at least one rematch file.
174
+ */
175
+ export function roundsProgression(
176
+ ledger: LedgerRow[],
177
+ rounds: RematchRow[][],
178
+ ): Map<string, RoundState[]> {
179
+ const byIid = new Map<string, RoundState[]>()
180
+ const rematchIids = new Set(rounds.flat().map((r) => r.iid))
181
+ for (const r of ledger) {
182
+ if (!rematchIids.has(r.iid)) continue
183
+ byIid.set(r.iid, [
184
+ {
185
+ round: 'SUP',
186
+ resolved: r.sup_resolved === true,
187
+ verifyPass: r.sup_verify_pass === true,
188
+ patchLines: r.sup_patch_lines,
189
+ verdict: r.sup_verdict,
190
+ delivered: r.sup_delivered,
191
+ spentTokens: r.sup_spentTokens,
192
+ },
193
+ ])
194
+ }
195
+ for (const round of rounds) {
196
+ for (const r of round) {
197
+ const states = byIid.get(r.iid)
198
+ if (!states) throw new Error(`rematch row for ${r.iid} has no ledger baseline`)
199
+ states.push({
200
+ round: r.arm,
201
+ resolved: r.resolved === true,
202
+ verifyPass: r.verify_pass === true,
203
+ patchLines: r.patch_lines,
204
+ verdict: r.sup_verdict,
205
+ delivered: r.delivered,
206
+ spentTokens: r.spentTokens,
207
+ })
208
+ }
209
+ }
210
+ return byIid
211
+ }