@velum-labs/routekit-eval-setup 1.3.2 → 1.5.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 (342) hide show
  1. package/dist/adapters/authoring-responses-request.d.ts +5 -0
  2. package/dist/adapters/authoring-responses-request.js +38 -0
  3. package/dist/adapters/evaluation-evidence-freshness.d.ts +7 -0
  4. package/dist/adapters/evaluation-evidence-freshness.js +76 -0
  5. package/dist/adapters/git-task-history.d.ts +67 -0
  6. package/dist/adapters/git-task-history.js +171 -0
  7. package/dist/adapters/integrated-repository-history.d.ts +21 -0
  8. package/dist/adapters/integrated-repository-history.js +175 -0
  9. package/dist/adapters/repository-command-diagnostic.d.ts +8 -0
  10. package/dist/adapters/repository-command-diagnostic.js +46 -0
  11. package/dist/adapters/repository-command-evidence.d.ts +13 -0
  12. package/dist/adapters/repository-command-evidence.js +102 -0
  13. package/dist/adapters/repository-command-runner.d.ts +238 -0
  14. package/dist/adapters/repository-command-runner.js +1483 -0
  15. package/dist/adapters/repository-import-context.d.ts +47 -0
  16. package/dist/adapters/repository-import-context.js +469 -0
  17. package/dist/adapters/repository-node-test-reporter.d.ts +3 -0
  18. package/dist/adapters/repository-node-test-reporter.js +27 -0
  19. package/dist/adapters/repository-review-evidence.d.ts +39 -0
  20. package/dist/adapters/repository-review-evidence.js +632 -0
  21. package/dist/adapters/repository-seed-selection.d.ts +7 -0
  22. package/dist/adapters/repository-seed-selection.js +79 -0
  23. package/dist/adapters/repository-solution-edits.d.ts +49 -0
  24. package/dist/adapters/repository-solution-edits.js +136 -0
  25. package/dist/adapters/repository-vitest-phase-adapter.d.ts +8 -0
  26. package/dist/adapters/repository-vitest-phase-adapter.js +310 -0
  27. package/dist/adapters/repository-vitest-reporter.d.ts +24 -0
  28. package/dist/adapters/repository-vitest-reporter.js +314 -0
  29. package/dist/adapters/strict-authoring-schema.d.ts +5 -0
  30. package/dist/adapters/strict-authoring-schema.js +158 -0
  31. package/dist/adapters/test-discovery.d.ts +30 -0
  32. package/dist/adapters/test-discovery.js +124 -0
  33. package/dist/adapters/typescript-repository-index.d.ts +51 -0
  34. package/dist/adapters/typescript-repository-index.js +226 -0
  35. package/dist/agentic-capabilities-protocol.d.ts +1373 -0
  36. package/dist/agentic-capabilities-protocol.js +786 -0
  37. package/dist/case-checkpoint-store.d.ts +29 -0
  38. package/dist/case-checkpoint-store.js +133 -0
  39. package/dist/case-pipeline-protocol-v2.d.ts +184 -0
  40. package/dist/case-pipeline-protocol-v2.js +193 -0
  41. package/dist/case-pipeline-protocol.d.ts +2626 -0
  42. package/dist/case-pipeline-protocol.js +371 -0
  43. package/dist/effect-api.d.ts +74 -10
  44. package/dist/effect-api.js +56 -6
  45. package/dist/errors.d.ts +31 -0
  46. package/dist/errors.js +10 -0
  47. package/dist/eval-capability-execution-envelope.d.ts +64 -0
  48. package/dist/eval-capability-execution-envelope.js +98 -0
  49. package/dist/eval-capability-policy.d.ts +90 -0
  50. package/dist/eval-capability-policy.js +107 -0
  51. package/dist/eval-event-log.d.ts +140 -0
  52. package/dist/eval-event-log.js +220 -0
  53. package/dist/evaluation-authoring-policy.d.ts +18 -0
  54. package/dist/evaluation-authoring-policy.js +19 -0
  55. package/dist/evaluation-authoring-validation.d.ts +22 -0
  56. package/dist/evaluation-authoring-validation.js +72 -0
  57. package/dist/evaluation-evidence.d.ts +20 -0
  58. package/dist/evaluation-evidence.js +319 -0
  59. package/dist/evaluation-grader-calibration-protocol.d.ts +108 -0
  60. package/dist/evaluation-grader-calibration-protocol.js +80 -0
  61. package/dist/evaluation-grader-calibration.d.ts +18 -0
  62. package/dist/evaluation-grader-calibration.js +334 -0
  63. package/dist/evaluation-grading-policy.d.ts +24 -0
  64. package/dist/evaluation-grading-policy.js +54 -0
  65. package/dist/evaluation-proposal-policy.d.ts +4 -0
  66. package/dist/evaluation-proposal-policy.js +91 -0
  67. package/dist/evaluation-source-retrieval.d.ts +68 -0
  68. package/dist/evaluation-source-retrieval.js +513 -0
  69. package/dist/evaluation-structure-policy.d.ts +29 -0
  70. package/dist/evaluation-structure-policy.js +138 -0
  71. package/dist/index.d.ts +124 -17
  72. package/dist/index.js +69 -11
  73. package/dist/inspection.js +2 -3
  74. package/dist/project-artifacts.d.ts +7 -2
  75. package/dist/project-artifacts.js +49 -136
  76. package/dist/project-authoring.d.ts +66 -5
  77. package/dist/project-authoring.js +783 -109
  78. package/dist/project-contracts.d.ts +419 -84
  79. package/dist/project-contracts.js +160 -52
  80. package/dist/project-store.js +2 -1
  81. package/dist/project-workflow.d.ts +5 -4
  82. package/dist/project-workflow.js +154 -35
  83. package/dist/repository-adversary-protocol.d.ts +64 -0
  84. package/dist/repository-adversary-protocol.js +105 -0
  85. package/dist/repository-behavior-protocol.d.ts +188 -0
  86. package/dist/repository-behavior-protocol.js +202 -0
  87. package/dist/repository-benchmark-protocol.d.ts +487 -0
  88. package/dist/repository-benchmark-protocol.js +96 -0
  89. package/dist/repository-execution-protocol.d.ts +150 -0
  90. package/dist/repository-execution-protocol.js +38 -0
  91. package/dist/repository-fixture-instructions.d.ts +3 -0
  92. package/dist/repository-fixture-instructions.js +91 -0
  93. package/dist/repository-fixture-protocol.d.ts +79 -0
  94. package/dist/repository-fixture-protocol.js +79 -0
  95. package/dist/repository-foundry-plan-protocol.d.ts +118 -0
  96. package/dist/repository-foundry-plan-protocol.js +296 -0
  97. package/dist/repository-foundry-progress-protocol.d.ts +52 -0
  98. package/dist/repository-foundry-progress-protocol.js +52 -0
  99. package/dist/repository-improvement-protocol.d.ts +100 -0
  100. package/dist/repository-improvement-protocol.js +106 -0
  101. package/dist/repository-language-model-protocol.d.ts +43 -0
  102. package/dist/repository-language-model-protocol.js +146 -0
  103. package/dist/repository-oracle-coverage-protocol.d.ts +18 -0
  104. package/dist/repository-oracle-coverage-protocol.js +39 -0
  105. package/dist/repository-oracle-execution-binding.d.ts +27 -0
  106. package/dist/repository-oracle-execution-binding.js +59 -0
  107. package/dist/repository-oracle-protocol.d.ts +230 -0
  108. package/dist/repository-oracle-protocol.js +156 -0
  109. package/dist/repository-oracle-scope-policy.d.ts +22 -0
  110. package/dist/repository-oracle-scope-policy.js +92 -0
  111. package/dist/repository-quality-policy.d.ts +15 -0
  112. package/dist/repository-quality-policy.js +357 -0
  113. package/dist/repository-routing-benchmark-protocol.d.ts +176 -0
  114. package/dist/repository-routing-benchmark-protocol.js +103 -0
  115. package/dist/repository-routing-model-protocol.d.ts +36 -0
  116. package/dist/repository-routing-model-protocol.js +89 -0
  117. package/dist/repository-routing-plan-protocol.d.ts +112 -0
  118. package/dist/repository-routing-plan-protocol.js +58 -0
  119. package/dist/repository-routing-quality-policy.d.ts +9 -0
  120. package/dist/repository-routing-quality-policy.js +191 -0
  121. package/dist/repository-seed-qualification-progress-protocol.d.ts +205 -0
  122. package/dist/repository-seed-qualification-progress-protocol.js +28 -0
  123. package/dist/repository-semantic-calibration-protocol.d.ts +768 -0
  124. package/dist/repository-semantic-calibration-protocol.js +276 -0
  125. package/dist/repository-semantic-calibration.d.ts +163 -0
  126. package/dist/repository-semantic-calibration.js +581 -0
  127. package/dist/repository-specification-contract-facts-protocol.d.ts +224 -0
  128. package/dist/repository-specification-contract-facts-protocol.js +276 -0
  129. package/dist/repository-specification-critique-protocol.d.ts +189 -0
  130. package/dist/repository-specification-critique-protocol.js +103 -0
  131. package/dist/repository-task-family-protocol.d.ts +24 -0
  132. package/dist/repository-task-family-protocol.js +37 -0
  133. package/dist/repository-task-seed-protocol.d.ts +384 -0
  134. package/dist/repository-task-seed-protocol.js +236 -0
  135. package/dist/repository-trajectory-protocol.d.ts +20 -0
  136. package/dist/repository-trajectory-protocol.js +42 -0
  137. package/dist/service.js +1 -1
  138. package/dist/services/adversary/service.d.ts +64 -0
  139. package/dist/services/adversary/service.js +330 -0
  140. package/dist/services/benchmark-compiler/service.d.ts +450 -0
  141. package/dist/services/benchmark-compiler/service.js +9 -0
  142. package/dist/services/budgeted-model/service.d.ts +118 -0
  143. package/dist/services/budgeted-model/service.js +460 -0
  144. package/dist/services/case-authoring/service.d.ts +163 -0
  145. package/dist/services/case-authoring/service.js +1456 -0
  146. package/dist/services/case-finalization/service.d.ts +283 -0
  147. package/dist/services/case-finalization/service.js +370 -0
  148. package/dist/services/case-generation/service.d.ts +619 -0
  149. package/dist/services/case-generation/service.js +2628 -0
  150. package/dist/services/case-pipeline/service.d.ts +31 -0
  151. package/dist/services/case-pipeline/service.js +485 -0
  152. package/dist/services/case-pipeline-v2/service.d.ts +70 -0
  153. package/dist/services/case-pipeline-v2/service.js +477 -0
  154. package/dist/services/command-observability/service.d.ts +13 -0
  155. package/dist/services/command-observability/service.js +3 -0
  156. package/dist/services/dimension-labeling/service.d.ts +77 -0
  157. package/dist/services/dimension-labeling/service.js +188 -0
  158. package/dist/services/eval-candidate/service.d.ts +208 -0
  159. package/dist/services/eval-candidate/service.js +64 -0
  160. package/dist/services/eval-capabilities/service.d.ts +183 -0
  161. package/dist/services/eval-capabilities/service.js +1433 -0
  162. package/dist/services/eval-environment/service.d.ts +173 -0
  163. package/dist/services/eval-environment/service.js +127 -0
  164. package/dist/services/evidence-reconstruction/service.d.ts +36 -0
  165. package/dist/services/evidence-reconstruction/service.js +145 -0
  166. package/dist/services/fixture-builder/service.d.ts +62 -0
  167. package/dist/services/fixture-builder/service.js +36 -0
  168. package/dist/services/fixture-validation/service.d.ts +75 -0
  169. package/dist/services/fixture-validation/service.js +295 -0
  170. package/dist/services/foundry/service.d.ts +831 -0
  171. package/dist/services/foundry/service.js +442 -0
  172. package/dist/services/foundry-progress/service.d.ts +62 -0
  173. package/dist/services/foundry-progress/service.js +149 -0
  174. package/dist/services/foundry-v2/service.d.ts +54 -0
  175. package/dist/services/foundry-v2/service.js +28 -0
  176. package/dist/services/grounded-authoring/service.d.ts +126 -0
  177. package/dist/services/grounded-authoring/service.js +822 -0
  178. package/dist/services/historical-case/service.d.ts +722 -0
  179. package/dist/services/historical-case/service.js +177 -0
  180. package/dist/services/improvement-loop/service.d.ts +59 -0
  181. package/dist/services/improvement-loop/service.js +176 -0
  182. package/dist/services/language-model/service.d.ts +52 -0
  183. package/dist/services/language-model/service.js +194 -0
  184. package/dist/services/oracle-builder/service.d.ts +146 -0
  185. package/dist/services/oracle-builder/service.js +513 -0
  186. package/dist/services/oracle-coverage/service.d.ts +28 -0
  187. package/dist/services/oracle-coverage/service.js +50 -0
  188. package/dist/services/oracle-coverage-witness/service.d.ts +130 -0
  189. package/dist/services/oracle-coverage-witness/service.js +538 -0
  190. package/dist/services/pipeline-challenge/service.d.ts +551 -0
  191. package/dist/services/pipeline-challenge/service.js +427 -0
  192. package/dist/services/pipeline-controls/service.d.ts +130 -0
  193. package/dist/services/pipeline-controls/service.js +483 -0
  194. package/dist/services/pipeline-oracle/service.d.ts +8 -0
  195. package/dist/services/pipeline-oracle/service.js +256 -0
  196. package/dist/services/pipeline-seed/service.d.ts +298 -0
  197. package/dist/services/pipeline-seed/service.js +428 -0
  198. package/dist/services/pipeline-spec/service.d.ts +103 -0
  199. package/dist/services/pipeline-spec/service.js +619 -0
  200. package/dist/services/pipeline-tournament/service.d.ts +258 -0
  201. package/dist/services/pipeline-tournament/service.js +476 -0
  202. package/dist/services/quality-gate/service.d.ts +233 -0
  203. package/dist/services/quality-gate/service.js +136 -0
  204. package/dist/services/repository-bundle/service.d.ts +33 -0
  205. package/dist/services/repository-bundle/service.js +114 -0
  206. package/dist/services/repository-model/service.d.ts +105 -0
  207. package/dist/services/repository-model/service.js +250 -0
  208. package/dist/services/repository-public-artifact/service.d.ts +133 -0
  209. package/dist/services/repository-public-artifact/service.js +330 -0
  210. package/dist/services/routing-benchmark/service.d.ts +362 -0
  211. package/dist/services/routing-benchmark/service.js +96 -0
  212. package/dist/services/specification-critic/service.d.ts +92 -0
  213. package/dist/services/specification-critic/service.js +172 -0
  214. package/dist/services/task-family/service.d.ts +40 -0
  215. package/dist/services/task-family/service.js +55 -0
  216. package/dist/services/task-seed/service.d.ts +906 -0
  217. package/dist/services/task-seed/service.js +1406 -0
  218. package/dist/services/task-specification/service.d.ts +27 -0
  219. package/dist/services/task-specification/service.js +40 -0
  220. package/dist/services/trajectory-policy/service.d.ts +110 -0
  221. package/dist/services/trajectory-policy/service.js +216 -0
  222. package/dist/test/agentic-capabilities-protocol.test.d.ts +1 -0
  223. package/dist/test/agentic-capabilities-protocol.test.js +570 -0
  224. package/dist/test/agentic-capabilities.test.d.ts +1 -0
  225. package/dist/test/agentic-capabilities.test.js +1461 -0
  226. package/dist/test/agentic-environment.test.d.ts +1 -0
  227. package/dist/test/agentic-environment.test.js +213 -0
  228. package/dist/test/case-pipeline-foundation.test.d.ts +1 -0
  229. package/dist/test/case-pipeline-foundation.test.js +535 -0
  230. package/dist/test/case-pipeline-protocol-v2.test.d.ts +1 -0
  231. package/dist/test/case-pipeline-protocol-v2.test.js +124 -0
  232. package/dist/test/case-pipeline-v2.test.d.ts +1 -0
  233. package/dist/test/case-pipeline-v2.test.js +286 -0
  234. package/dist/test/case-pipeline.test.d.ts +1 -0
  235. package/dist/test/case-pipeline.test.js +851 -0
  236. package/dist/test/eval-capability-policy.test.d.ts +1 -0
  237. package/dist/test/eval-capability-policy.test.js +50 -0
  238. package/dist/test/eval-event-log.test.d.ts +1 -0
  239. package/dist/test/eval-event-log.test.js +125 -0
  240. package/dist/test/evaluation-evidence-freshness.test.d.ts +1 -0
  241. package/dist/test/evaluation-evidence-freshness.test.js +44 -0
  242. package/dist/test/evaluation-evidence.test.d.ts +1 -0
  243. package/dist/test/evaluation-evidence.test.js +230 -0
  244. package/dist/test/evaluation-grader-calibration.test.d.ts +1 -0
  245. package/dist/test/evaluation-grader-calibration.test.js +373 -0
  246. package/dist/test/evaluation-proposal-digest.test.d.ts +1 -0
  247. package/dist/test/evaluation-proposal-digest.test.js +187 -0
  248. package/dist/test/evaluation-source-retrieval.test.d.ts +1 -0
  249. package/dist/test/evaluation-source-retrieval.test.js +237 -0
  250. package/dist/test/evaluation-structure-policy.test.d.ts +1 -0
  251. package/dist/test/evaluation-structure-policy.test.js +196 -0
  252. package/dist/test/fixtures/repository-resource-panel.d.ts +39 -0
  253. package/dist/test/fixtures/repository-resource-panel.js +111 -0
  254. package/dist/test/fixtures/vitest-boundary-panel.d.ts +84 -0
  255. package/dist/test/fixtures/vitest-boundary-panel.js +120 -0
  256. package/dist/test/fixtures/vitest-phase-panel.d.ts +135 -0
  257. package/dist/test/fixtures/vitest-phase-panel.js +213 -0
  258. package/dist/test/fixtures/vitest-reporter-results.d.ts +76 -0
  259. package/dist/test/fixtures/vitest-reporter-results.js +94 -0
  260. package/dist/test/grounded-authoring.test.d.ts +1 -0
  261. package/dist/test/grounded-authoring.test.js +565 -0
  262. package/dist/test/integrated-repository-history.test.d.ts +1 -0
  263. package/dist/test/integrated-repository-history.test.js +227 -0
  264. package/dist/test/project-authoring.test.js +593 -43
  265. package/dist/test/project-workflow.test.js +419 -40
  266. package/dist/test/repository-authoring-artifacts.test.d.ts +1 -0
  267. package/dist/test/repository-authoring-artifacts.test.js +185 -0
  268. package/dist/test/repository-bundle.test.d.ts +1 -0
  269. package/dist/test/repository-bundle.test.js +52 -0
  270. package/dist/test/repository-case-generation.test.d.ts +1 -0
  271. package/dist/test/repository-case-generation.test.js +3465 -0
  272. package/dist/test/repository-command-diagnostic.test.d.ts +1 -0
  273. package/dist/test/repository-command-diagnostic.test.js +55 -0
  274. package/dist/test/repository-command-signals.test.d.ts +1 -0
  275. package/dist/test/repository-command-signals.test.js +124 -0
  276. package/dist/test/repository-fixture-scope-coverage.test.d.ts +1 -0
  277. package/dist/test/repository-fixture-scope-coverage.test.js +127 -0
  278. package/dist/test/repository-fixture-validation.test.d.ts +1 -0
  279. package/dist/test/repository-fixture-validation.test.js +362 -0
  280. package/dist/test/repository-foundry-progress.test.d.ts +1 -0
  281. package/dist/test/repository-foundry-progress.test.js +110 -0
  282. package/dist/test/repository-foundry-quality.test.d.ts +1 -0
  283. package/dist/test/repository-foundry-quality.test.js +1138 -0
  284. package/dist/test/repository-import-context.test.d.ts +1 -0
  285. package/dist/test/repository-import-context.test.js +354 -0
  286. package/dist/test/repository-model-authoring.test.d.ts +1 -0
  287. package/dist/test/repository-model-authoring.test.js +544 -0
  288. package/dist/test/repository-model.test.d.ts +1 -0
  289. package/dist/test/repository-model.test.js +2195 -0
  290. package/dist/test/repository-node-test-reporter.test.d.ts +1 -0
  291. package/dist/test/repository-node-test-reporter.test.js +104 -0
  292. package/dist/test/repository-oracle-concurrency.test.d.ts +1 -0
  293. package/dist/test/repository-oracle-concurrency.test.js +542 -0
  294. package/dist/test/repository-oracle-coverage-witness.test.d.ts +1 -0
  295. package/dist/test/repository-oracle-coverage-witness.test.js +511 -0
  296. package/dist/test/repository-oracle-coverage.test.d.ts +1 -0
  297. package/dist/test/repository-oracle-coverage.test.js +168 -0
  298. package/dist/test/repository-oracle-evidence.test.d.ts +1 -0
  299. package/dist/test/repository-oracle-evidence.test.js +185 -0
  300. package/dist/test/repository-oracle-plan.test.d.ts +1 -0
  301. package/dist/test/repository-oracle-plan.test.js +176 -0
  302. package/dist/test/repository-overlay-isolation.test.d.ts +1 -0
  303. package/dist/test/repository-overlay-isolation.test.js +85 -0
  304. package/dist/test/repository-preparation-cache.test.d.ts +1 -0
  305. package/dist/test/repository-preparation-cache.test.js +414 -0
  306. package/dist/test/repository-public-artifact.test.d.ts +1 -0
  307. package/dist/test/repository-public-artifact.test.js +273 -0
  308. package/dist/test/repository-qualification-diagnostics.test.d.ts +1 -0
  309. package/dist/test/repository-qualification-diagnostics.test.js +524 -0
  310. package/dist/test/repository-reference-authoring.test.d.ts +1 -0
  311. package/dist/test/repository-reference-authoring.test.js +1633 -0
  312. package/dist/test/repository-review-evidence-v2.test.d.ts +1 -0
  313. package/dist/test/repository-review-evidence-v2.test.js +183 -0
  314. package/dist/test/repository-review-evidence.test.d.ts +1 -0
  315. package/dist/test/repository-review-evidence.test.js +124 -0
  316. package/dist/test/repository-seed-exclusions.test.d.ts +1 -0
  317. package/dist/test/repository-seed-exclusions.test.js +96 -0
  318. package/dist/test/repository-seed-selection.test.d.ts +1 -0
  319. package/dist/test/repository-seed-selection.test.js +504 -0
  320. package/dist/test/repository-semantic-calibration.test.d.ts +1 -0
  321. package/dist/test/repository-semantic-calibration.test.js +688 -0
  322. package/dist/test/repository-solution-edits.test.d.ts +1 -0
  323. package/dist/test/repository-solution-edits.test.js +377 -0
  324. package/dist/test/repository-specification-budget.test.d.ts +1 -0
  325. package/dist/test/repository-specification-budget.test.js +171 -0
  326. package/dist/test/repository-specification-contract-checkpoint.test.d.ts +1 -0
  327. package/dist/test/repository-specification-contract-checkpoint.test.js +228 -0
  328. package/dist/test/repository-specification-contract-facts.test.d.ts +1 -0
  329. package/dist/test/repository-specification-contract-facts.test.js +177 -0
  330. package/dist/test/repository-trajectory-authoring.test.d.ts +1 -0
  331. package/dist/test/repository-trajectory-authoring.test.js +176 -0
  332. package/dist/test/repository-valid-control-plan.test.d.ts +1 -0
  333. package/dist/test/repository-valid-control-plan.test.js +45 -0
  334. package/dist/test/repository-vitest-phase.test.d.ts +1 -0
  335. package/dist/test/repository-vitest-phase.test.js +848 -0
  336. package/dist/test/repository-vitest-reporter.test.d.ts +1 -0
  337. package/dist/test/repository-vitest-reporter.test.js +158 -0
  338. package/dist/test/repository-workspace-build.test.d.ts +1 -0
  339. package/dist/test/repository-workspace-build.test.js +160 -0
  340. package/dist/test/strict-authoring-schema.test.d.ts +1 -0
  341. package/dist/test/strict-authoring-schema.test.js +169 -0
  342. package/package.json +48 -6
@@ -4,11 +4,14 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { after, test } from "node:test";
6
6
  import { layer as NodeServicesLayer } from "@effect/platform-node/NodeServices";
7
+ import { CostServiceLive, makeCostService } from "@velum-labs/routekit-registry";
7
8
  import { Cause, Effect, Layer } from "effect";
8
9
  import { EvalDimensionContrastError, EvalModelCatalogError } from "../errors.js";
10
+ import { compileEvaluationEvidence, renderEvaluationEvidenceContext } from "../evaluation-evidence.js";
11
+ import { renderEvaluationCriteria } from "../evaluation-structure-policy.js";
9
12
  import { EvalRepositoryInspectorLive } from "../inspection.js";
10
13
  import { EvalProjectArtifactsLive } from "../project-artifacts.js";
11
- import { scopedExpectedCalls, summarizeEvalRunLedger } from "../project-contracts.js";
14
+ import { estimateEvalPlanCost, scopedExpectedCalls, summarizeEvalRunLedger } from "../project-contracts.js";
12
15
  import { EvalProjectStoreLive } from "../project-store.js";
13
16
  import { EvalProjectWorkflow, EvalProjectWorkflowLive } from "../project-workflow.js";
14
17
  import { EvalModelCatalog } from "../services/model-catalog/service.js";
@@ -28,12 +31,13 @@ const catalogEfforts = (candidateModels) => Effect.sync(() => {
28
31
  };
29
32
  });
30
33
  const EvalModelCatalogTest = Layer.succeed(EvalModelCatalog, EvalModelCatalog.of({ reasoningEfforts: catalogEfforts }));
31
- const ProjectWorkflowTestLive = EvalProjectWorkflowLive.pipe(Layer.provide(ProjectDependenciesLive), Layer.provide(EvalModelCatalogTest), Layer.provide(NodeServicesLayer));
34
+ const ProjectWorkflowTestLive = EvalProjectWorkflowLive.pipe(Layer.provide(ProjectDependenciesLive), Layer.provide(EvalModelCatalogTest), Layer.provide(CostServiceLive), Layer.provide(NodeServicesLayer));
35
+ const testCostService = makeCostService();
32
36
  const makeRepository = async () => {
33
37
  const root = await mkdtemp(path.join(os.tmpdir(), "routekit-eval-project-"));
34
38
  roots.push(root);
35
39
  await mkdir(path.join(root, "src"), { recursive: true });
36
- await writeFile(path.join(root, "README.md"), "# Example application\n");
40
+ await writeFile(path.join(root, "README.md"), "# Example application\n\nA terminal event must be last; later output is invalid and unsupported.\n");
37
41
  await writeFile(path.join(root, "src", "model.ts"), 'client.responses.create({ model: "openai/current" });\n');
38
42
  return root;
39
43
  };
@@ -162,7 +166,7 @@ test("candidate selection fails closed when catalog reasoning metadata is unavai
162
166
  detail: "models.list did not advertise candidate reasoning capabilities"
163
167
  }))
164
168
  }));
165
- const workflowLayer = EvalProjectWorkflowLive.pipe(Layer.provide(ProjectDependenciesLive), Layer.provide(unavailableCatalog), Layer.provide(NodeServicesLayer));
169
+ const workflowLayer = EvalProjectWorkflowLive.pipe(Layer.provide(ProjectDependenciesLive), Layer.provide(unavailableCatalog), Layer.provide(CostServiceLive), Layer.provide(NodeServicesLayer));
166
170
  await Effect.runPromise(Effect.gen(function* () {
167
171
  const workflow = yield* EvalProjectWorkflow;
168
172
  yield* workflow.setup(root);
@@ -203,15 +207,89 @@ const reviewedProposedDimensions = reviewedDimensions.map((dimension, index) =>
203
207
  inScopeRequest: `Solve an in-scope request for dimension ${String(index + 1)}.`,
204
208
  nearMissRequest: `Solve a neighboring request outside dimension ${String(index + 1)}.`
205
209
  }));
210
+ const reviewedPrompt = (dimensionId, index) => {
211
+ switch (index % 5) {
212
+ case 0:
213
+ return `Transform raw ${dimensionId} capture ${String(index + 1)}`;
214
+ case 1:
215
+ return `Locate the fault in ${dimensionId} trace ${String(index + 1)}`;
216
+ case 2:
217
+ return `Construct the ${dimensionId} artifact for sample ${String(index + 1)}`;
218
+ case 3:
219
+ return `Analyze the ${dimensionId} boundary sample ${String(index + 1)}`;
220
+ default:
221
+ return `Explain the ${dimensionId} operational result ${String(index + 1)}`;
222
+ }
223
+ };
224
+ const reviewedEvidenceSources = compileEvaluationEvidence({
225
+ sources: [
226
+ {
227
+ path: "README.md",
228
+ content: "# Example application\n\nA terminal event must be last; later output is invalid and unsupported.\n"
229
+ },
230
+ {
231
+ path: "src/model.ts",
232
+ content: 'client.responses.create({ model: "openai/current" });\n'
233
+ }
234
+ ],
235
+ maximumBlockBytes: 3_000
236
+ });
237
+ const reviewedEvidenceBlock = reviewedEvidenceSources[0].blocks[0];
238
+ const reviewedCompiledFields = (caseId, index) => {
239
+ const criterionId = `${caseId}-criterion`;
240
+ const criteria = [
241
+ {
242
+ id: criterionId,
243
+ description: `Derive the observable repository behavior for case ${String(index + 1)}.`,
244
+ importance: "critical",
245
+ gradingMode: "semantic"
246
+ }
247
+ ];
248
+ return {
249
+ context: renderEvaluationEvidenceContext([reviewedEvidenceBlock]),
250
+ rubric: renderEvaluationCriteria(criteria),
251
+ evidenceIds: [reviewedEvidenceBlock.id],
252
+ criteria,
253
+ positiveControls: [
254
+ {
255
+ id: `${caseId}-positive`,
256
+ response: `The response derives the repository-backed behavior for case ${String(index + 1)}.`
257
+ }
258
+ ],
259
+ negativeControls: [
260
+ {
261
+ id: `${caseId}-missing`,
262
+ response: "The response discusses the topic but omits the required behavior.",
263
+ targetedCriterionIds: [criterionId],
264
+ kind: "missing-criterion"
265
+ },
266
+ {
267
+ id: `${caseId}-forbidden`,
268
+ response: "The response invents behavior contradicted by the repository evidence.",
269
+ targetedCriterionIds: [criterionId],
270
+ kind: "forbidden-behavior"
271
+ },
272
+ {
273
+ id: `${caseId}-non-responsive`,
274
+ response: "This is a polished overview that never answers the concrete request.",
275
+ targetedCriterionIds: [criterionId],
276
+ kind: "non-responsive"
277
+ }
278
+ ]
279
+ };
280
+ };
206
281
  const reviewedSuites = reviewedDimensions.map((dimension) => ({
207
282
  version: 1,
208
283
  dimensionId: dimension.id,
209
284
  maximumOutputTokens: 256,
210
- cases: Array.from({ length: 20 }, (_, index) => ({
211
- id: `${dimension.id}-case-${String(index + 1)}`,
212
- prompt: `Solve ${dimension.id} case ${String(index + 1)}`,
213
- rubric: `The response must satisfy ${dimension.id} case ${String(index + 1)}`
214
- }))
285
+ cases: Array.from({ length: 20 }, (_, index) => {
286
+ const id = `${dimension.id}-case-${String(index + 1)}`;
287
+ return {
288
+ id,
289
+ prompt: reviewedPrompt(dimension.id, index),
290
+ ...reviewedCompiledFields(id, index)
291
+ };
292
+ })
215
293
  }));
216
294
  const reviewedDecompositionBenchmark = {
217
295
  maximumVectorL1Error: 0.3,
@@ -231,32 +309,53 @@ const reviewedCompositionSuite = {
231
309
  maximumOutputTokens: 256,
232
310
  minimumWinnerScoreGap: 0.05,
233
311
  minimumWinnerAgreement: 0.8,
234
- cases: Array.from({ length: 20 }, (_, index) => ({
235
- id: `composition-case-${String(index + 1)}`,
236
- prompt: `Solve multi-dimension production case ${String(index + 1)}`,
237
- rubric: `The response must satisfy multi-dimension case ${String(index + 1)}`,
238
- decomposition: {
239
- weights: reviewedDimensions.map((dimension, dimensionIndex) => ({
240
- dimensionId: dimension.id,
241
- weight: dimensionIndex === index % reviewedDimensions.length ||
242
- dimensionIndex === (index + 1) % reviewedDimensions.length
243
- ? 0.5
244
- : 0
245
- })),
246
- unknownWeight: 0
247
- },
248
- requirements: {
249
- endpoint: "responses",
250
- requiresTools: false,
251
- requiresVision: false
252
- }
253
- }))
312
+ cases: Array.from({ length: 20 }, (_, index) => {
313
+ const id = `composition-case-${String(index + 1)}`;
314
+ return {
315
+ id,
316
+ prompt: reviewedPrompt("multi-dimension", index),
317
+ ...reviewedCompiledFields(id, index),
318
+ decomposition: {
319
+ weights: reviewedDimensions.map((dimension, dimensionIndex) => ({
320
+ dimensionId: dimension.id,
321
+ weight: dimensionIndex === index % reviewedDimensions.length ||
322
+ dimensionIndex === (index + 1) % reviewedDimensions.length
323
+ ? 0.5
324
+ : 0
325
+ })),
326
+ unknownWeight: 0
327
+ },
328
+ requirements: {
329
+ endpoint: "responses",
330
+ requiresTools: false,
331
+ requiresVision: false
332
+ }
333
+ };
334
+ })
254
335
  };
255
336
  const reviewedEvaluationInput = {
337
+ evidenceSources: reviewedEvidenceSources,
256
338
  suites: reviewedSuites,
257
339
  decompositionBenchmark: reviewedDecompositionBenchmark,
258
340
  compositionSuite: reviewedCompositionSuite
259
341
  };
342
+ const flashcardEvaluationInput = {
343
+ ...reviewedEvaluationInput,
344
+ suites: reviewedSuites.map((suite) => ({
345
+ ...suite,
346
+ cases: Array.from({ length: 20 }, (_, index) => {
347
+ const scenario = Math.floor(index / 2);
348
+ return {
349
+ id: `${suite.dimensionId}-flashcard-${String(index + 1)}`,
350
+ prompt: index % 2 === 0
351
+ ? "Diagnose the described RouteKit behavior. Use only the supplied context."
352
+ : "Give a concise verification criterion. Use only the supplied context.",
353
+ context: `Captured scenario ${String(scenario + 1)} emitted output after completion. The terminal event must be last.`,
354
+ rubric: "Reject post-terminal output and require completion to be the final event."
355
+ };
356
+ })
357
+ }))
358
+ };
260
359
  async function completeProjectSetup(root, candidateModels = "openai/gpt-5.6-luna openai/gpt-5.6-terra openai/gpt-5.6-sol") {
261
360
  await Effect.runPromise(Effect.gen(function* () {
262
361
  const workflow = yield* EvalProjectWorkflow;
@@ -356,7 +455,8 @@ test("reviewed artifacts are digest-bound and produce an immutable exact-call pl
356
455
  assert.equal(ready.state.stage, "ready");
357
456
  assert.equal(ready.nextAction, "run");
358
457
  const plan = yield* workflow.createPlan(root, "pilot");
359
- return { basisDigest, evaluationDigest, plan };
458
+ const repeatedPlan = yield* workflow.createPlan(root, "pilot", 2);
459
+ return { basisDigest, evaluationDigest, plan, repeatedPlan };
360
460
  }).pipe(Effect.provide(ProjectWorkflowTestLive)));
361
461
  const storedBasis = JSON.parse(await readFile(path.join(root, ".routekit", "evals", "routing-basis.proposed.json"), "utf8"));
362
462
  assert.equal(storedBasis.dimensionContrasts.length, reviewedDimensions.length);
@@ -374,20 +474,47 @@ test("reviewed artifacts are digest-bound and produce an immutable exact-call pl
374
474
  assert.equal(result.plan.expectedJudgeCalls, 90);
375
475
  assert.equal(result.plan.expectedCallCount, 185);
376
476
  assert.equal(result.plan.maximumOutputTokens, 256);
477
+ assert.equal(result.plan.repetitions, 1);
478
+ assert.equal(result.repeatedPlan.repetitions, 2);
479
+ assert.equal(result.repeatedPlan.expectedCallCount, result.plan.expectedCallCount * 2);
480
+ assert.equal(result.plan.costEstimate !== undefined, true);
481
+ assert.equal(result.repeatedPlan.costEstimate !== undefined, true);
482
+ assert.equal(result.repeatedPlan.costEstimate?.knownPricedMaximumUsd, (result.plan.costEstimate?.knownPricedMaximumUsd ?? 0) * 2);
377
483
  const planPath = path.join(root, ".routekit", "evals", "plans", `${result.plan.planId}.json`);
378
484
  assert.equal((await stat(planPath)).mode & 0o777, 0o600);
379
485
  assert.deepEqual(JSON.parse(await readFile(planPath, "utf8")), result.plan);
380
486
  const firstDimension = reviewedDimensions[0];
381
487
  const generatedSuite = await readFile(path.join(root, ".routekit", "evals", "plans", result.plan.planId, "dimensions", firstDimension.id, `${firstDimension.id}.eval.ts`), "utf8");
488
+ const candidateCases = JSON.parse(await readFile(path.join(root, ".routekit", "evals", "plans", result.plan.planId, "dimensions", firstDimension.id, "data", "candidate-cases.json"), "utf8"));
489
+ const hiddenOracles = JSON.parse(await readFile(path.join(root, ".routekit", "evals", "plans", result.plan.planId, "dimensions", firstDimension.id, "data", "hidden-oracles.json"), "utf8"));
382
490
  const completionAssertion = generatedSuite.indexOf("run.toComplete();");
491
+ const oracleLookup = generatedSuite.indexOf("hiddenOracleById.get(candidateCase.id)");
383
492
  const judgeCall = generatedSuite.indexOf("await judge.autoEvals");
384
493
  const completionRethrow = generatedSuite.indexOf("throw candidateCompletionError;");
385
494
  const judgeRethrow = generatedSuite.indexOf("throw judgeError;");
386
495
  assert.match(generatedSuite, /emit the complete target-protocol envelope and terminal event/u);
387
496
  assert.match(generatedSuite, /include the concrete output rather than only describing it/u);
497
+ assert.match(generatedSuite, /You are a strict evaluator for AI agent outputs\./u);
498
+ assert.deepEqual(Object.keys(candidateCases[0]).sort(), [
499
+ "context",
500
+ "id",
501
+ "prompt"
502
+ ]);
503
+ assert.equal(Object.hasOwn(candidateCases[0], "rubric"), false);
504
+ assert.equal(Object.hasOwn(candidateCases[0], "criteria"), false);
505
+ assert.deepEqual(Object.keys(hiddenOracles[0]).sort(), [
506
+ "criteria",
507
+ "id",
508
+ "negativeControls",
509
+ "positiveControls",
510
+ "rubric"
511
+ ]);
512
+ assert.equal(candidateCases[0].id, hiddenOracles[0].id);
388
513
  assert.ok(judgeCall >= 0);
514
+ assert.ok(oracleLookup >= 0);
389
515
  assert.ok(completionAssertion >= 0);
390
- assert.ok(completionAssertion < judgeCall);
516
+ assert.ok(completionAssertion < oracleLookup);
517
+ assert.ok(oracleLookup < judgeCall);
391
518
  assert.ok(judgeCall < completionRethrow);
392
519
  assert.ok(completionRethrow < judgeRethrow);
393
520
  for (const dimension of reviewedDimensions) {
@@ -395,6 +522,95 @@ test("reviewed artifacts are digest-bound and produce an immutable exact-call pl
395
522
  .mode & 0o777, 0o600);
396
523
  }
397
524
  });
525
+ test("evaluation approval rejects historical cases without compiler provenance", async () => {
526
+ const root = await makeRepository();
527
+ await completeProjectSetup(root);
528
+ const exit = await Effect.runPromise(Effect.gen(function* () {
529
+ const workflow = yield* EvalProjectWorkflow;
530
+ const proposedBasis = yield* workflow.proposeDimensions(root, reviewedProposedDimensions);
531
+ yield* workflow.approveDimensions(root, proposedBasis.artifacts.basisProposalDigest);
532
+ const proposed = yield* workflow.proposeEvaluations(root, flashcardEvaluationInput);
533
+ return yield* Effect.exit(workflow.approveEvaluations(root, proposed.artifacts.evaluationProposalDigest));
534
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
535
+ assert.equal(exit._tag, "Failure");
536
+ if (exit._tag === "Failure") {
537
+ const error = Cause.squash(exit.cause);
538
+ assert.match(String(error), /is not compiler-backed/u);
539
+ }
540
+ const approvalPath = path.join(root, ".routekit", "evals", "evaluations.approved.json");
541
+ await assert.rejects(stat(approvalPath), /ENOENT/u);
542
+ });
543
+ test("repository evidence changes stale approval before approval and planning", async () => {
544
+ const beforeApprovalRoot = await makeRepository();
545
+ await completeProjectSetup(beforeApprovalRoot);
546
+ const beforeApproval = await Effect.runPromise(Effect.gen(function* () {
547
+ const workflow = yield* EvalProjectWorkflow;
548
+ const proposedBasis = yield* workflow.proposeDimensions(beforeApprovalRoot, reviewedProposedDimensions);
549
+ yield* workflow.approveDimensions(beforeApprovalRoot, proposedBasis.artifacts.basisProposalDigest);
550
+ const proposed = yield* workflow.proposeEvaluations(beforeApprovalRoot, reviewedEvaluationInput);
551
+ return proposed.artifacts.evaluationProposalDigest;
552
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
553
+ await writeFile(path.join(beforeApprovalRoot, "README.md"), "# Example application\n\nChanged after authoring.\n");
554
+ const approvalExit = await Effect.runPromise(Effect.gen(function* () {
555
+ const workflow = yield* EvalProjectWorkflow;
556
+ return yield* Effect.exit(workflow.approveEvaluations(beforeApprovalRoot, beforeApproval));
557
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
558
+ assert.equal(approvalExit._tag, "Failure");
559
+ if (approvalExit._tag === "Failure") {
560
+ assert.match(String(Cause.squash(approvalExit.cause)), /evidence source changed after authoring/u);
561
+ }
562
+ const beforePlanRoot = await makeRepository();
563
+ await completeProjectSetup(beforePlanRoot);
564
+ await Effect.runPromise(Effect.gen(function* () {
565
+ const workflow = yield* EvalProjectWorkflow;
566
+ const proposedBasis = yield* workflow.proposeDimensions(beforePlanRoot, reviewedProposedDimensions);
567
+ yield* workflow.approveDimensions(beforePlanRoot, proposedBasis.artifacts.basisProposalDigest);
568
+ const proposed = yield* workflow.proposeEvaluations(beforePlanRoot, reviewedEvaluationInput);
569
+ yield* workflow.approveEvaluations(beforePlanRoot, proposed.artifacts.evaluationProposalDigest);
570
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
571
+ await writeFile(path.join(beforePlanRoot, "README.md"), "# Example application\n\nChanged before planning.\n");
572
+ const stale = await Effect.runPromise(Effect.gen(function* () {
573
+ const workflow = yield* EvalProjectWorkflow;
574
+ const status = yield* workflow.status(beforePlanRoot);
575
+ const planExit = yield* Effect.exit(workflow.createPlan(beforePlanRoot, "pilot"));
576
+ return { status, planExit };
577
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
578
+ assert.equal(stale.status?.artifacts?.evaluationsApproved, false);
579
+ assert.equal(stale.status?.nextAction, "approve-evaluations");
580
+ assert.equal(stale.planExit._tag, "Failure");
581
+ if (stale.planExit._tag === "Failure") {
582
+ assert.match(String(stale.planExit.cause), /evidence source changed after authoring/u);
583
+ }
584
+ });
585
+ test("ready projects can explicitly rebind stale evaluation approvals", async () => {
586
+ const root = await makeRepository();
587
+ await completeProjectSetup(root);
588
+ const rebound = await Effect.runPromise(Effect.gen(function* () {
589
+ const workflow = yield* EvalProjectWorkflow;
590
+ const proposedBasis = yield* workflow.proposeDimensions(root, reviewedProposedDimensions);
591
+ yield* workflow.approveDimensions(root, proposedBasis.artifacts.basisProposalDigest);
592
+ const proposed = yield* workflow.proposeEvaluations(root, reviewedEvaluationInput);
593
+ const digest = proposed.artifacts.evaluationProposalDigest;
594
+ const ready = yield* workflow.approveEvaluations(root, digest);
595
+ yield* Effect.promise(() => writeFile(path.join(root, ".routekit", "evals", "evaluations.approval.json"), `${JSON.stringify({
596
+ version: 1,
597
+ kind: "evaluations",
598
+ digest,
599
+ approvedAt: "2026-08-30T00:00:00.000Z"
600
+ })}\n`, { mode: 0o600 }));
601
+ const stale = yield* workflow.status(root);
602
+ const reboundStatus = yield* workflow.approveEvaluations(root, digest);
603
+ return { ready, stale, reboundStatus };
604
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
605
+ assert.equal(rebound.ready.state.stage, "ready");
606
+ assert.ok(rebound.stale !== undefined);
607
+ assert.equal(rebound.stale.state.stage, "ready");
608
+ assert.equal(rebound.stale.artifacts?.evaluationsApproved, false);
609
+ assert.equal(rebound.stale.nextAction, "approve-evaluations");
610
+ assert.equal(rebound.reboundStatus.state.stage, "ready");
611
+ assert.equal(rebound.reboundStatus.artifacts?.evaluationsApproved, true);
612
+ assert.equal(rebound.reboundStatus.nextAction, "run");
613
+ });
398
614
  test("full qualification refuses fewer than twenty reviewed cases per dimension", async () => {
399
615
  const root = await makeRepository();
400
616
  await completeProjectSetup(root);
@@ -403,6 +619,7 @@ test("full qualification refuses fewer than twenty reviewed cases per dimension"
403
619
  const proposed = yield* workflow.proposeDimensions(root, reviewedProposedDimensions);
404
620
  yield* workflow.approveDimensions(root, proposed.artifacts.basisProposalDigest);
405
621
  const evaluations = yield* workflow.proposeEvaluations(root, {
622
+ evidenceSources: reviewedEvidenceSources,
406
623
  suites: reviewedSuites.map((suite) => ({ ...suite, cases: suite.cases.slice(0, 5) })),
407
624
  decompositionBenchmark: {
408
625
  ...reviewedDecompositionBenchmark,
@@ -555,18 +772,25 @@ const qualifiedReport = (plan, runId, projectId, publishAllowed = true) => {
555
772
  expectedCases: plan.selectedCompositionCaseIds.length,
556
773
  comparableCases: plan.selectedCompositionCaseIds.length,
557
774
  agreeingCases: plan.selectedCompositionCaseIds.length,
558
- winnerAgreement: 1,
775
+ ...(plan.selectedCompositionCaseIds.length === 0
776
+ ? {}
777
+ : { winnerAgreement: 1 }),
778
+ conclusion: plan.selectedCompositionCaseIds.length === 0 ? "inconclusive" : "passed",
779
+ publishable: plan.scope === "full" && plan.selectedCompositionCaseIds.length > 0,
559
780
  cases: plan.selectedCompositionCaseIds.map((caseId) => ({
560
781
  caseId,
561
782
  predictedWinner: candidateModels[0],
783
+ predictedTiedModels: [],
562
784
  observedWinner: candidateModels[0],
785
+ observedTiedModels: [],
563
786
  predictedScoreGap: 0.1,
564
787
  observedScoreGap: 0.2,
788
+ comparable: true,
565
789
  passed: true
566
790
  }))
567
791
  }
568
792
  },
569
- ledger: summarizeEvalRunLedger(comparisons, plan.expectedCallCount, observations),
793
+ ledger: Effect.runSync(summarizeEvalRunLedger(comparisons, plan.expectedCallCount, testCostService, observations, plan.classifierModel)),
570
794
  activation: {
571
795
  version: 2,
572
796
  generatedAt: "2026-08-18T00:02:00.000Z",
@@ -590,7 +814,8 @@ const qualifiedReport = (plan, runId, projectId, publishAllowed = true) => {
590
814
  failureRate: 0,
591
815
  averageJudgeScore: 0.9,
592
816
  p95DurationMs: 250,
593
- unpricedCalls: plan.scope === "pilot" ? 5 : 20
817
+ averageCostUsd: 0.01,
818
+ unpricedCalls: 0
594
819
  })))
595
820
  }
596
821
  };
@@ -624,7 +849,7 @@ test("run lifecycle retains complete sanitized evidence and activates only after
624
849
  assert.equal(outcome.loaded?.status, "passed");
625
850
  assert.equal(outcome.loaded?.ledger.observedCalls, 740);
626
851
  assert.equal(outcome.loaded?.ledger.observedCandidateRows, 360);
627
- assert.equal(outcome.loaded?.ledger.unpricedCalls, 740);
852
+ assert.equal(outcome.loaded?.ledger.unpricedCalls, 0);
628
853
  assert.equal(outcome.activated.state.stage, "activated");
629
854
  const reportPath = path.join(root, ".routekit", "evals", "runs", outcome.started.runId, "report.json");
630
855
  assert.equal((await stat(reportPath)).mode & 0o777, 0o600);
@@ -633,8 +858,9 @@ test("finishRun derives ledger expectations from the selected case slice", async
633
858
  const root = await makeRepository();
634
859
  const created = await createPlan(root, "full", "openai/gpt-5.6-luna openai/gpt-5.6-terra");
635
860
  const selectedDimension = created.selectedCaseIds[0];
636
- const stalePlan = {
861
+ const stalePlanInput = {
637
862
  ...created,
863
+ repetitions: 1,
638
864
  selectedCaseIds: [
639
865
  {
640
866
  dimensionId: selectedDimension.dimensionId,
@@ -652,6 +878,10 @@ test("finishRun derives ledger expectations from the selected case slice", async
652
878
  expectedJudgeCalls: 3_400,
653
879
  expectedCallCount: 6_820
654
880
  };
881
+ const stalePlan = {
882
+ ...stalePlanInput,
883
+ costEstimate: Effect.runSync(estimateEvalPlanCost(stalePlanInput, testCostService))
884
+ };
655
885
  await writeFile(path.join(root, ".routekit", "evals", "plans", `${created.planId}.json`), `${JSON.stringify(stalePlan)}\n`);
656
886
  const outcome = await Effect.runPromise(Effect.gen(function* () {
657
887
  const workflow = yield* EvalProjectWorkflow;
@@ -671,7 +901,7 @@ test("finishRun derives ledger expectations from the selected case slice", async
671
901
  });
672
902
  const scopedReport = {
673
903
  ...report,
674
- ledger: summarizeEvalRunLedger(report.comparisons, expected.expectedCallCount)
904
+ ledger: yield* summarizeEvalRunLedger(report.comparisons, expected.expectedCallCount, testCostService)
675
905
  };
676
906
  const finished = yield* workflow.finishRun(root, scopedReport);
677
907
  return { finished, report: scopedReport };
@@ -681,6 +911,98 @@ test("finishRun derives ledger expectations from the selected case slice", async
681
911
  assert.equal(outcome.report.ledger.observedCalls, 4);
682
912
  assert.equal(outcome.report.ledger.observedCandidateRows, 2);
683
913
  });
914
+ test("zero-comparable pilot persists a completed inconclusive diagnostic", async () => {
915
+ const root = await makeRepository();
916
+ const plan = await createPlan(root, "pilot");
917
+ const outcome = await Effect.runPromise(Effect.gen(function* () {
918
+ const workflow = yield* EvalProjectWorkflow;
919
+ const started = yield* workflow.startRun(root, plan.planId);
920
+ const status = yield* workflow.status(root);
921
+ const passing = qualifiedReport(plan, started.runId, status.state.projectId);
922
+ assert.equal(passing.status, "passed");
923
+ if (passing.status !== "passed")
924
+ return undefined;
925
+ const report = {
926
+ ...passing,
927
+ status: "completed",
928
+ qualification: {
929
+ ...passing.qualification,
930
+ composition: {
931
+ expectedCases: plan.selectedCompositionCaseIds.length,
932
+ comparableCases: 0,
933
+ agreeingCases: 0,
934
+ conclusion: "inconclusive",
935
+ reason: "insufficient top-two separation",
936
+ publishable: false,
937
+ cases: plan.selectedCompositionCaseIds.map((caseId) => ({
938
+ caseId,
939
+ predictedWinner: null,
940
+ predictedTiedModels: [...plan.candidateModels],
941
+ observedWinner: null,
942
+ observedTiedModels: [...plan.candidateModels],
943
+ predictedScoreGap: 0,
944
+ observedScoreGap: 0,
945
+ comparable: false,
946
+ passed: false
947
+ }))
948
+ }
949
+ }
950
+ };
951
+ const finished = yield* workflow.finishRun(root, report);
952
+ const persisted = yield* workflow.result(root, started.runId);
953
+ return { finished, persisted };
954
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
955
+ assert.equal(outcome?.finished.state.stage, "ready");
956
+ assert.equal(outcome?.persisted?.status, "completed");
957
+ assert.equal(outcome?.persisted?.qualification?.composition.expectedCases, 5);
958
+ assert.equal(outcome?.persisted?.qualification?.composition.comparableCases, 0);
959
+ assert.equal(outcome?.persisted?.qualification?.composition.winnerAgreement, undefined);
960
+ assert.equal(outcome?.persisted?.qualification?.composition.conclusion, "inconclusive");
961
+ assert.equal(outcome?.persisted?.qualification?.composition.publishable, false);
962
+ });
963
+ test("zero-comparable full qualification remains fail closed and unpublished", async () => {
964
+ const root = await makeRepository();
965
+ const plan = await createPlan(root, "full");
966
+ const outcome = await Effect.runPromise(Effect.gen(function* () {
967
+ const workflow = yield* EvalProjectWorkflow;
968
+ const started = yield* workflow.startRun(root, plan.planId);
969
+ const status = yield* workflow.status(root);
970
+ const passing = qualifiedReport(plan, started.runId, status.state.projectId);
971
+ assert.equal(passing.status, "passed");
972
+ if (passing.status !== "passed")
973
+ return undefined;
974
+ const report = {
975
+ ...passing,
976
+ qualification: {
977
+ ...passing.qualification,
978
+ composition: {
979
+ expectedCases: plan.selectedCompositionCaseIds.length,
980
+ comparableCases: 0,
981
+ agreeingCases: 0,
982
+ conclusion: "inconclusive",
983
+ reason: "insufficient top-two separation",
984
+ publishable: false,
985
+ cases: plan.selectedCompositionCaseIds.map((caseId) => ({
986
+ caseId,
987
+ predictedWinner: null,
988
+ predictedTiedModels: [...plan.candidateModels],
989
+ observedWinner: null,
990
+ observedTiedModels: [...plan.candidateModels],
991
+ predictedScoreGap: 0,
992
+ observedScoreGap: 0,
993
+ comparable: false,
994
+ passed: false
995
+ }))
996
+ }
997
+ }
998
+ };
999
+ const finished = yield* Effect.exit(workflow.finishRun(root, report));
1000
+ const current = yield* workflow.status(root);
1001
+ return { finished, current };
1002
+ }).pipe(Effect.provide(ProjectWorkflowTestLive)));
1003
+ assert.equal(outcome?.finished._tag, "Failure");
1004
+ assert.equal(outcome?.current?.state.stage, "running");
1005
+ });
684
1006
  test("failed runs preserve plan revisions for retry while mismatched plans remain stale", async () => {
685
1007
  const root = await makeRepository();
686
1008
  const plan = await createPlan(root, "full");
@@ -738,8 +1060,36 @@ test("failed runs preserve plan revisions for retry while mismatched plans remai
738
1060
  knownOutputTokens: 0,
739
1061
  unknownTokenMeasurements: 0,
740
1062
  knownPricedSubtotalUsd: 0,
741
- unpricedCalls: 0
1063
+ unpricedCalls: 0,
1064
+ subscriptionUnitCalls: 0
742
1065
  },
1066
+ events: [
1067
+ {
1068
+ name: "plan",
1069
+ at: "2026-08-21T03:47:27.000Z",
1070
+ runId: first.runId,
1071
+ plannedCalls: plan.expectedCallCount
1072
+ },
1073
+ {
1074
+ name: "failure-detail",
1075
+ at: "2026-08-21T03:47:48.000Z",
1076
+ runId: first.runId,
1077
+ phase: "target",
1078
+ reason: "qualification timed out before observing any calls",
1079
+ retryable: true,
1080
+ accounting: {
1081
+ expectedCalls: plan.expectedCallCount,
1082
+ observedCalls: 0,
1083
+ knownInputTokens: 0,
1084
+ knownOutputTokens: 0,
1085
+ unknownTokenMeasurements: 0,
1086
+ knownPricedSubtotalUsd: 0,
1087
+ unpricedCalls: 0,
1088
+ subscriptionUnitCalls: 0
1089
+ },
1090
+ cleanup: { sessionOpened: true, sessionClosed: true }
1091
+ }
1092
+ ],
743
1093
  failure: "qualification timed out before observing any calls",
744
1094
  errors: []
745
1095
  });
@@ -764,7 +1114,7 @@ test("startRun rematerializes stale dimension and composition suite templates",
764
1114
  const dimensionManifestPath = path.join(dimensionRoot, "routekit.eval-manifest.json");
765
1115
  const compositionManifestPath = path.join(compositionRoot, "routekit.eval-manifest.json");
766
1116
  const staleTemplate = `// generated by RouteKit 1.0.21
767
- await judge.autoEvals({ criteria: testCase.rubric, prompt, run });
1117
+ await judge.autoEvals({ criteria: mixedCase.rubric, prompt, run });
768
1118
  run.toComplete();
769
1119
  `;
770
1120
  await Promise.all([
@@ -793,6 +1143,7 @@ run.toComplete();
793
1143
  assert.doesNotMatch(suite, /generated by RouteKit 1\.0\.21/u);
794
1144
  assert.match(suite, /emit the complete target-protocol envelope and terminal event/u);
795
1145
  assert.match(suite, /include the concrete output rather than only describing it/u);
1146
+ assert.match(suite, /You are a strict evaluator for AI agent outputs\./u);
796
1147
  assert.ok(judgeCall >= 0);
797
1148
  assert.ok(completionAssertion >= 0);
798
1149
  assert.ok(completionAssertion < judgeCall);
@@ -842,8 +1193,36 @@ test("failed run reports persist the nested qualification error chain", async ()
842
1193
  knownOutputTokens: 0,
843
1194
  unknownTokenMeasurements: 0,
844
1195
  knownPricedSubtotalUsd: 0,
845
- unpricedCalls: 0
1196
+ unpricedCalls: 0,
1197
+ subscriptionUnitCalls: 0
846
1198
  },
1199
+ events: [
1200
+ {
1201
+ name: "plan",
1202
+ at: "2026-08-21T03:47:27.000Z",
1203
+ runId: started.runId,
1204
+ plannedCalls: plan.expectedCallCount
1205
+ },
1206
+ {
1207
+ name: "failure-detail",
1208
+ at: "2026-08-21T03:47:48.000Z",
1209
+ runId: started.runId,
1210
+ phase: "comparison",
1211
+ reason: "RouteKit Eval comparison failed",
1212
+ retryable: false,
1213
+ accounting: {
1214
+ expectedCalls: plan.expectedCallCount,
1215
+ observedCalls: 0,
1216
+ knownInputTokens: 0,
1217
+ knownOutputTokens: 0,
1218
+ unknownTokenMeasurements: 0,
1219
+ knownPricedSubtotalUsd: 0,
1220
+ unpricedCalls: 0,
1221
+ subscriptionUnitCalls: 0
1222
+ },
1223
+ cleanup: { sessionOpened: true, sessionClosed: true }
1224
+ }
1225
+ ],
847
1226
  failure: 'qualification dimension "provider-protocol-translation" failed ' +
848
1227
  "(per-test timeout 600000ms); observed call ids: none",
849
1228
  errors: [
@@ -0,0 +1 @@
1
+ export {};