@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
@@ -0,0 +1,535 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtemp, readdir, readFile, rm, stat, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { test } from "node:test";
6
+ import { Effect, Fiber, Layer, Option, Schema } from "effect";
7
+ import { TestClock } from "effect/testing";
8
+ import { makeCaseCheckpointStoreV1 } from "../case-checkpoint-store.js";
9
+ import { checkpointDigestV1, makePipelineCaseContextV1, nextPipelineStageV1, PIPELINE_STAGES, PipelineBudgetExhaustedError, pipelineScientificIdentityDigestV1, pipelineStageToProgressStageV1 } from "../case-pipeline-protocol.js";
10
+ import { RepositoryFoundryError } from "../errors.js";
11
+ import { BudgetedLanguageModelLive, budgetExhaustedErrorOfV1, budgetSnapshotV1, generateWithFeedbackV1, isStructuredOutputInvalidV1, makePipelineStateAccessorV1, structuredOutputInvalidResponseTextV1 } from "../services/budgeted-model/service.js";
12
+ import { runCasePipelineV1 } from "../services/case-pipeline/service.js";
13
+ import { reportFoundryAuthoringArtifactV1 } from "../services/foundry-progress/service.js";
14
+ import { RepositoryFoundryLanguageModel } from "../services/language-model/service.js";
15
+ const Tiny = Schema.Struct({ version: Schema.Literal(1), stage: Schema.String, n: Schema.Finite });
16
+ const Answer = Schema.Struct({ answer: Schema.String });
17
+ const assignment = {
18
+ role: "spec-writer",
19
+ model: "openai/gpt-6-astra",
20
+ reasoningEffort: "high"
21
+ };
22
+ const dimension = { id: "dim", description: "A dimension.", includes: [], excludes: [] };
23
+ const initialState = (overrides = {}) => ({
24
+ version: 1,
25
+ caseId: "case-1",
26
+ repositoryRoot: "/repo",
27
+ dimension,
28
+ model: "openai/gpt-6-astra",
29
+ stage: "spec",
30
+ status: "running",
31
+ startedAt: "2026-09-08T00:00:00.000Z",
32
+ updatedAt: "2026-09-08T00:00:00.000Z",
33
+ modelCalls: 0,
34
+ elapsedMs: 0,
35
+ resumeCount: 0,
36
+ folds: 0,
37
+ ...overrides
38
+ });
39
+ async function caseDirectory(t) {
40
+ const root = await mkdtemp(join(tmpdir(), "case-pipeline-foundation-"));
41
+ t.after(() => rm(root, { recursive: true, force: true }));
42
+ return join(root, "case");
43
+ }
44
+ /** A fake inner model; the wrapper must never see the transport directly. */
45
+ const fakeInner = (reply) => {
46
+ const state = { calls: 0 };
47
+ const generateAssignedStructured = (input) => Effect.gen(function* () {
48
+ state.calls += 1;
49
+ const raw = yield* reply({
50
+ operationId: input.operationId,
51
+ input: input.input,
52
+ attempt: state.calls
53
+ });
54
+ const value = yield* Schema.decodeUnknownEffect(input.outputSchema)(raw).pipe(Effect.mapError((cause) => new RepositoryFoundryError({
55
+ operation: "invoke-language-model",
56
+ detail: `language-model role ${input.assignment.role} violated ${input.schemaName}`,
57
+ cause
58
+ })));
59
+ return {
60
+ value,
61
+ call: {
62
+ operationId: input.operationId,
63
+ role: input.assignment.role,
64
+ model: input.assignment.model,
65
+ reasoningEffort: input.assignment.reasoningEffort,
66
+ callId: `call-${String(state.calls)}`
67
+ }
68
+ };
69
+ });
70
+ return {
71
+ get calls() {
72
+ return state.calls;
73
+ },
74
+ generateAssignedStructured,
75
+ generateStructured: (input) => generateAssignedStructured({
76
+ ...input,
77
+ assignment: { role: input.role, model: "openai/gpt-6-astra", reasoningEffort: "high" }
78
+ })
79
+ };
80
+ };
81
+ async function harness(t, options) {
82
+ const directory = await caseDirectory(t);
83
+ const store = makeCaseCheckpointStoreV1({ caseDirectory: directory });
84
+ const state = await Effect.runPromise(makePipelineStateAccessorV1(options.state ?? initialState()));
85
+ const sleeps = [];
86
+ const inner = fakeInner(options.reply);
87
+ const layer = BudgetedLanguageModelLive({
88
+ store,
89
+ state,
90
+ budget: options.budget ?? { maxModelCalls: 10, maxWallMs: 60 * 60_000 },
91
+ startedAtMs: Date.now(),
92
+ inner,
93
+ sleep: (ms) => Effect.sync(() => {
94
+ sleeps.push(ms);
95
+ })
96
+ });
97
+ const generate = (input = { ask: "value" }) => Effect.gen(function* () {
98
+ const model = yield* RepositoryFoundryLanguageModel;
99
+ return yield* model.generateAssignedStructured({
100
+ assignment,
101
+ operationId: "op",
102
+ instructions: "Answer.",
103
+ input,
104
+ schemaName: "Answer",
105
+ outputSchema: Answer,
106
+ maximumOutputTokens: 64
107
+ });
108
+ });
109
+ return { directory, store, state, sleeps, inner, layer, generate };
110
+ }
111
+ test("checkpoint store writes atomically, reads back, and deletes this stage and later stages", async (t) => {
112
+ const directory = await caseDirectory(t);
113
+ const store = makeCaseCheckpointStoreV1({ caseDirectory: directory });
114
+ await Effect.runPromise(Effect.gen(function* () {
115
+ for (const [index, stage] of PIPELINE_STAGES.entries()) {
116
+ yield* store.writeStage(stage, { version: 1, stage, n: index });
117
+ }
118
+ const oracle = yield* store.readStage("oracle", Tiny);
119
+ assert.deepEqual(oracle, Option.some({ version: 1, stage: "oracle", n: 2 }));
120
+ yield* store.deleteFrom("controls");
121
+ assert.deepEqual(yield* store.readStage("oracle", Tiny), Option.some({ version: 1, stage: "oracle", n: 2 }));
122
+ assert.deepEqual(yield* store.readStage("controls", Tiny), Option.none());
123
+ assert.deepEqual(yield* store.readStage("admit", Tiny), Option.none());
124
+ yield* store.writeState(initialState());
125
+ const state = yield* store.readState;
126
+ assert.equal(Option.isSome(state) && state.value.caseId, "case-1");
127
+ yield* store.writeCall("spec:writer/1", 2, { request: { a: 1 }, response: { b: 2 } });
128
+ yield* store.writeWorkCheckpoint("reference/qualification", {
129
+ version: 1,
130
+ stage: "seed",
131
+ n: 7
132
+ });
133
+ assert.deepEqual(yield* store.readWorkCheckpoint("reference/qualification", Tiny), Option.some({ version: 1, stage: "seed", n: 7 }));
134
+ yield* store.deleteWorkCheckpoint("reference/qualification");
135
+ assert.deepEqual(yield* store.readWorkCheckpoint("reference/qualification", Tiny), Option.none());
136
+ }));
137
+ const entries = await readdir(directory);
138
+ assert.ok(!entries.some((entry) => entry.endsWith(".tmp")), "no temporary files remain");
139
+ assert.equal((await stat(join(directory, "seed.json"))).mode & 0o777, 0o600);
140
+ assert.equal((await stat(directory)).mode & 0o777, 0o700);
141
+ const calls = await readdir(join(directory, "calls"));
142
+ assert.deepEqual(calls, ["spec_writer_1.attempt-2.json"]);
143
+ assert.deepEqual(JSON.parse(await readFile(join(directory, "calls", calls[0]), "utf8")), {
144
+ operationId: "spec:writer/1",
145
+ attempt: 2,
146
+ request: { a: 1 },
147
+ response: { b: 2 }
148
+ });
149
+ });
150
+ test("a checkpoint that fails schema decode or JSON parse reads as absent, never as a failure", async (t) => {
151
+ const directory = await caseDirectory(t);
152
+ const store = makeCaseCheckpointStoreV1({ caseDirectory: directory });
153
+ await Effect.runPromise(store.writeStage("seed", { version: 2, stage: "seed", n: "nope" }));
154
+ await writeFile(join(directory, "spec.json"), "{ not json", "utf8");
155
+ const [seed, spec, missing] = await Effect.runPromise(Effect.all([
156
+ store.readStage("seed", Tiny),
157
+ store.readStage("spec", Tiny),
158
+ store.readStage("admit", Tiny)
159
+ ]));
160
+ assert.deepEqual(seed, Option.none());
161
+ assert.deepEqual(spec, Option.none());
162
+ assert.deepEqual(missing, Option.none());
163
+ });
164
+ test("budget exhaustion fails before the inner model is called", async (t) => {
165
+ const run = await harness(t, {
166
+ reply: () => Effect.succeed({ answer: "never" }),
167
+ state: initialState({ modelCalls: 3 }),
168
+ budget: { maxModelCalls: 3, maxWallMs: 60_000 }
169
+ });
170
+ const exit = await Effect.runPromise(run.generate().pipe(Effect.provide(run.layer), Effect.flip));
171
+ const exhausted = budgetExhaustedErrorOfV1(exit);
172
+ assert.ok(exhausted instanceof PipelineBudgetExhaustedError);
173
+ assert.equal(exhausted.modelCalls, 3);
174
+ assert.equal(exhausted.stage, "spec");
175
+ assert.equal(run.inner.calls, 0);
176
+ const ledger = await Effect.runPromise(run.store.readLedger);
177
+ assert.deepEqual(ledger, []);
178
+ });
179
+ test("transport failures are retried with backoff and ledgered, then the received call is counted once", async (t) => {
180
+ const run = await harness(t, {
181
+ reply: ({ attempt }) => attempt <= 2
182
+ ? Effect.fail(new RepositoryFoundryError({
183
+ operation: "invoke-language-model",
184
+ detail: attempt === 1
185
+ ? "language-model role spec-writer failed: RouteKit Eval could not complete authoring-evaluations: author model request failed before response headers: unknown"
186
+ : `language-model role spec-writer failed: Transport error: fetch failed (attempt ${String(attempt)})`
187
+ }))
188
+ : Effect.succeed({ answer: "ok" })
189
+ });
190
+ const result = await Effect.runPromise(Effect.gen(function* () {
191
+ const generated = yield* run.generate();
192
+ const snapshot = yield* budgetSnapshotV1;
193
+ return { generated, snapshot };
194
+ }).pipe(Effect.provide(run.layer)));
195
+ assert.deepEqual(result.generated.value, { answer: "ok" });
196
+ assert.equal(result.generated.call.callId, "call-3");
197
+ assert.deepEqual(run.sleeps, [5_000, 15_000]);
198
+ assert.equal(result.snapshot.modelCalls, 1);
199
+ assert.equal(result.snapshot.remainingCalls, 9);
200
+ assert.equal(run.inner.calls, 3);
201
+ const persisted = await Effect.runPromise(run.store.readState);
202
+ assert.equal(Option.isSome(persisted) && persisted.value.modelCalls, 1);
203
+ const ledger = await Effect.runPromise(run.store.readLedger);
204
+ assert.deepEqual(ledger.map((entry) => [entry.phase, entry.attempt]), [
205
+ ["start", 1],
206
+ ["retry", 1],
207
+ ["start", 2],
208
+ ["retry", 2],
209
+ ["start", 3],
210
+ ["received", 3],
211
+ ["validated", 3]
212
+ ]);
213
+ assert.ok(ledger.every((entry) => entry.role === "spec-writer" && entry.model === assignment.model));
214
+ assert.equal(ledger[5].callId, "call-3");
215
+ assert.ok((ledger[0].inputBytes ?? 0) > 0);
216
+ assert.ok((ledger[5].outputBytes ?? 0) > 0);
217
+ });
218
+ test("a transport retry that cannot fit in the wall budget fails as exhausted, not as a case failure", async (t) => {
219
+ const run = await harness(t, {
220
+ reply: () => Effect.fail(new RepositoryFoundryError({
221
+ operation: "invoke-language-model",
222
+ detail: "language-model role spec-writer failed: connect ECONNREFUSED 127.0.0.1:8787"
223
+ })),
224
+ budget: { maxModelCalls: 10, maxWallMs: 4_000 }
225
+ });
226
+ const error = await Effect.runPromise(run.generate().pipe(Effect.provide(run.layer), Effect.flip));
227
+ assert.ok(budgetExhaustedErrorOfV1(error) instanceof PipelineBudgetExhaustedError);
228
+ assert.deepEqual(run.sleeps, []);
229
+ assert.equal(run.inner.calls, 1);
230
+ });
231
+ test("the orchestrator interrupts a host stage at the remaining cumulative wall deadline without persisting its checkpoint", async (t) => {
232
+ const directory = await caseDirectory(t);
233
+ const store = makeCaseCheckpointStoreV1({ caseDirectory: directory });
234
+ const context = makePipelineCaseContextV1({
235
+ repositoryRoot: "/repo",
236
+ caseId: "case-host-wall",
237
+ caseDirectory: directory,
238
+ dimension,
239
+ primaryModel: "openai/gpt-6-astra",
240
+ budget: { maxModelCalls: 10, maxWallMs: 100 }
241
+ });
242
+ const map = {
243
+ version: 1,
244
+ repository: { root: "/repo", commit: "head", requestedRef: "HEAD", tree: "tree" },
245
+ packages: [],
246
+ publicSurfaces: [],
247
+ protocols: [],
248
+ commands: [],
249
+ tests: [],
250
+ testBindings: [],
251
+ historyEpisodes: [],
252
+ workloadSignals: [],
253
+ blindSpots: []
254
+ };
255
+ const prior = initialState({
256
+ caseId: context.caseId,
257
+ repositoryRoot: context.repositoryRoot,
258
+ identityDigest: pipelineScientificIdentityDigestV1(context, map),
259
+ elapsedMs: 75
260
+ });
261
+ const inner = {
262
+ generateAssignedStructured: () => Effect.never,
263
+ generateStructured: () => Effect.never
264
+ };
265
+ await Effect.runPromise(store.writeState(prior));
266
+ const result = await Effect.runPromise(Effect.gen(function* () {
267
+ const fiber = yield* runCasePipelineV1({ context, map, store }).pipe(Effect.forkChild);
268
+ yield* TestClock.adjust(25);
269
+ return yield* Fiber.join(fiber);
270
+ }).pipe(Effect.provide(Layer.mergeAll(Layer.succeed(RepositoryFoundryLanguageModel, inner), TestClock.layer()))));
271
+ assert.equal(result.state.status, "exhausted");
272
+ assert.equal(result.state.stage, "seed");
273
+ assert.equal(result.state.modelCalls, 0);
274
+ assert.equal(result.state.elapsedMs, 100);
275
+ assert.match(result.state.reason ?? "", /exhausted its budget during seed/u);
276
+ assert.deepEqual(await Effect.runPromise(store.readStage("seed", Tiny)), Option.none());
277
+ const extended = { ...context, budget: { maxModelCalls: 10, maxWallMs: 200 } };
278
+ const resumed = await Effect.runPromise(Effect.gen(function* () {
279
+ const fiber = yield* runCasePipelineV1({ context: extended, map, store }).pipe(Effect.forkChild);
280
+ yield* TestClock.adjust(100);
281
+ return yield* Fiber.join(fiber);
282
+ }).pipe(Effect.provide(Layer.mergeAll(Layer.succeed(RepositoryFoundryLanguageModel, inner), TestClock.layer()))));
283
+ assert.equal(resumed.state.status, "exhausted");
284
+ assert.equal(resumed.state.elapsedMs, 200, "a resume receives only the extended remainder");
285
+ assert.ok(resumed.state.resumeCount >= 1);
286
+ assert.deepEqual(await Effect.runPromise(store.readStage("seed", Tiny)), Option.none());
287
+ });
288
+ test("received-but-invalid responses count as model calls and surface as structured-output-invalid", async (t) => {
289
+ const run = await harness(t, {
290
+ reply: () => Effect.succeed({ answer: 42 })
291
+ });
292
+ const error = await Effect.runPromise(run.generate().pipe(Effect.provide(run.layer), Effect.flip));
293
+ assert.ok(error instanceof RepositoryFoundryError);
294
+ assert.ok(isStructuredOutputInvalidV1(error));
295
+ assert.match(error.detail, /^structured-output-invalid: language-model role spec-writer violated Answer/u);
296
+ const persisted = await Effect.runPromise(run.store.readState);
297
+ assert.equal(Option.isSome(persisted) && persisted.value.modelCalls, 1);
298
+ assert.equal(run.sleeps.length, 0);
299
+ const ledger = await Effect.runPromise(run.store.readLedger);
300
+ assert.deepEqual(ledger.map((entry) => entry.phase), ["start", "received", "failed"]);
301
+ });
302
+ test("a response observed through the progress artifact is counted and its raw text is kept for feedback", async (t) => {
303
+ const run = await harness(t, {
304
+ reply: ({ operationId }) => Effect.gen(function* () {
305
+ yield* reportFoundryAuthoringArtifactV1({
306
+ version: 1,
307
+ operationId,
308
+ role: "spec-writer",
309
+ model: assignment.model,
310
+ reasoningEffort: "high",
311
+ schemaName: "Answer",
312
+ validation: "received",
313
+ request: { instructions: "Answer.", input: {}, jsonSchema: {}, maximumOutputTokens: 64 },
314
+ response: { text: "{ answer: unquoted }", callId: "provider-1" }
315
+ });
316
+ return yield* new RepositoryFoundryError({
317
+ operation: "invoke-language-model",
318
+ detail: "unexpected decoder failure without a known marker"
319
+ });
320
+ })
321
+ });
322
+ const error = await Effect.runPromise(run.generate().pipe(Effect.provide(run.layer), Effect.flip));
323
+ assert.ok(isStructuredOutputInvalidV1(error));
324
+ assert.equal(structuredOutputInvalidResponseTextV1(error), "{ answer: unquoted }");
325
+ const persisted = await Effect.runPromise(run.store.readState);
326
+ assert.equal(Option.isSome(persisted) && persisted.value.modelCalls, 1);
327
+ const ledger = await Effect.runPromise(run.store.readLedger);
328
+ assert.equal(ledger[1].phase, "received");
329
+ assert.equal(ledger[1].callId, "provider-1");
330
+ const calls = await readdir(join(run.directory, "calls"));
331
+ const saved = JSON.parse(await readFile(join(run.directory, "calls", calls[0]), "utf8"));
332
+ assert.equal(saved.response.text, "{ answer: unquoted }");
333
+ });
334
+ test("generateWithFeedbackV1 re-calls the same role with the prior output and findings until validation is clean", async (t) => {
335
+ const seen = [];
336
+ const run = await harness(t, {
337
+ reply: ({ operationId, input, attempt }) => Effect.sync(() => {
338
+ seen.push({ operationId, input });
339
+ if (attempt === 1)
340
+ return "not even an object";
341
+ if (attempt === 2)
342
+ return { answer: "too short" };
343
+ return { answer: "a complete answer" };
344
+ })
345
+ });
346
+ const result = await Effect.runPromise(generateWithFeedbackV1({
347
+ assignment,
348
+ operationId: "spec:analysis",
349
+ instructions: "Answer.",
350
+ input: { ask: "value" },
351
+ schemaName: "Answer",
352
+ outputSchema: Answer,
353
+ maximumOutputTokens: 64,
354
+ validate: (value) => Effect.succeed(value.answer.length < 12
355
+ ? [{ kind: "contract", message: "answer must be complete", path: "answer" }]
356
+ : [])
357
+ }).pipe(Effect.provide(run.layer)));
358
+ assert.deepEqual(result.value, { answer: "a complete answer" });
359
+ assert.equal(result.attempts, 3);
360
+ assert.equal(result.feedback.length, 2);
361
+ assert.deepEqual(seen.map((entry) => entry.operationId), ["spec:analysis", "spec:analysis:attempt-2", "spec:analysis:attempt-3"]);
362
+ assert.deepEqual(seen[0].input, { ask: "value" });
363
+ const second = seen[1].input;
364
+ assert.equal(second.ask, "value");
365
+ assert.equal(second.hostFeedback.attempt, 1);
366
+ assert.equal(second.hostFeedback.findings[0].kind, "schema");
367
+ const third = seen[2].input;
368
+ assert.equal(third.hostFeedback.attempt, 2);
369
+ assert.deepEqual(third.hostFeedback.priorOutput, { answer: "too short" });
370
+ assert.deepEqual(third.hostFeedback.findings, [
371
+ { kind: "contract", message: "answer must be complete", path: "answer" }
372
+ ]);
373
+ const snapshot = await Effect.runPromise(budgetSnapshotV1.pipe(Effect.provide(run.layer)));
374
+ assert.equal(snapshot.modelCalls, 3);
375
+ });
376
+ test("generateWithFeedbackV1 reports an unresolved loop as a recoverable foundry error and honours the global budget", async (t) => {
377
+ const run = await harness(t, {
378
+ reply: () => Effect.succeed({ answer: "x" }),
379
+ budget: { maxModelCalls: 2, maxWallMs: 60_000 }
380
+ });
381
+ const unresolved = await Effect.runPromise(generateWithFeedbackV1({
382
+ assignment,
383
+ operationId: "loop",
384
+ instructions: "Answer.",
385
+ input: [1, 2],
386
+ schemaName: "Answer",
387
+ outputSchema: Answer,
388
+ maximumOutputTokens: 64,
389
+ maxAttempts: 2,
390
+ validate: () => Effect.succeed([{ kind: "critique", message: "never good" }])
391
+ }).pipe(Effect.provide(run.layer), Effect.flip));
392
+ assert.ok(unresolved instanceof RepositoryFoundryError);
393
+ assert.match(unresolved.detail, /^feedback-loop-unresolved: critique: never good/u);
394
+ const exhausted = await Effect.runPromise(generateWithFeedbackV1({
395
+ assignment,
396
+ operationId: "loop-2",
397
+ instructions: "Answer.",
398
+ input: [1, 2],
399
+ schemaName: "Answer",
400
+ outputSchema: Answer,
401
+ maximumOutputTokens: 64,
402
+ validate: () => Effect.succeed([{ kind: "critique", message: "never good" }])
403
+ }).pipe(Effect.provide(run.layer), Effect.flip));
404
+ assert.ok(exhausted instanceof PipelineBudgetExhaustedError);
405
+ });
406
+ test("feedback continuation revalidates a received response after interruption without buying another call", async (t) => {
407
+ const run = await harness(t, {
408
+ reply: () => Effect.succeed({ answer: "retained answer" }),
409
+ budget: { maxModelCalls: 1, maxWallMs: 60_000 }
410
+ });
411
+ let interrupt = true;
412
+ let validations = 0;
413
+ const invoke = () => generateWithFeedbackV1({
414
+ assignment,
415
+ operationId: "durable:control",
416
+ instructions: "Answer.",
417
+ input: { task: "value" },
418
+ schemaName: "Answer",
419
+ outputSchema: Answer,
420
+ maximumOutputTokens: 64,
421
+ checkpoint: {
422
+ store: makeCaseCheckpointStoreV1({ caseDirectory: run.directory }),
423
+ binding: { commit: "pinned", validationRecipe: "sanity" }
424
+ },
425
+ validate: () => Effect.gen(function* () {
426
+ validations += 1;
427
+ if (interrupt)
428
+ return yield* Effect.interrupt;
429
+ return [];
430
+ })
431
+ }).pipe(Effect.provide(run.layer));
432
+ await Effect.runPromiseExit(invoke());
433
+ interrupt = false;
434
+ const resumed = await Effect.runPromise(invoke());
435
+ assert.deepEqual(resumed.value, { answer: "retained answer" });
436
+ assert.equal(resumed.attempts, 1);
437
+ assert.equal(validations, 2, "resumption must execute validation, not promote authoring to proof");
438
+ const snapshot = await Effect.runPromise(budgetSnapshotV1.pipe(Effect.provide(run.layer)));
439
+ assert.equal(snapshot.modelCalls, 1, "pending validation needs no remaining model budget");
440
+ });
441
+ test("feedback continuation preserves schema and host findings across reconstruction", async (t) => {
442
+ const seen = [];
443
+ let interrupted = false;
444
+ const run = await harness(t, {
445
+ reply: ({ input, attempt }) => {
446
+ seen.push(input);
447
+ if (attempt === 1)
448
+ return Effect.succeed("invalid schema");
449
+ if (attempt === 2)
450
+ return Effect.succeed({ answer: "needs repair" });
451
+ if (!interrupted) {
452
+ interrupted = true;
453
+ return Effect.interrupt;
454
+ }
455
+ return Effect.succeed({ answer: "complete" });
456
+ }
457
+ });
458
+ const invoke = () => generateWithFeedbackV1({
459
+ assignment,
460
+ operationId: "durable:findings",
461
+ instructions: "Answer.",
462
+ input: { task: "value" },
463
+ schemaName: "Answer",
464
+ outputSchema: Answer,
465
+ maximumOutputTokens: 64,
466
+ checkpoint: {
467
+ store: makeCaseCheckpointStoreV1({ caseDirectory: run.directory }),
468
+ binding: { validator: 1 }
469
+ },
470
+ validate: ({ answer }) => Effect.succeed(answer === "complete" ? [] : [
471
+ { kind: "execution", message: "concrete sanity failure" }
472
+ ])
473
+ }).pipe(Effect.provide(run.layer));
474
+ await Effect.runPromiseExit(invoke());
475
+ const resumed = await Effect.runPromise(invoke());
476
+ assert.equal(resumed.attempts, 3);
477
+ assert.equal(resumed.feedback.length, 2);
478
+ assert.equal(resumed.feedback[0].findings[0].kind, "schema");
479
+ assert.deepEqual(seen.at(-1), seen.at(-2));
480
+ assert.deepEqual(seen.at(-1).hostFeedback, {
481
+ attempt: 2,
482
+ findings: [{ kind: "execution", message: "concrete sanity failure" }],
483
+ priorOutput: { answer: "needs repair" }
484
+ });
485
+ });
486
+ test("feedback continuation invalidates changed host validation inputs even when the prompt is unchanged", async (t) => {
487
+ let calls = 0;
488
+ const run = await harness(t, {
489
+ reply: () => Effect.succeed({ answer: `response-${++calls}` })
490
+ });
491
+ const invoke = (binding) => generateWithFeedbackV1({
492
+ assignment,
493
+ operationId: "durable:binding",
494
+ instructions: "Answer.",
495
+ input: { task: "same-visible-task" },
496
+ schemaName: "Answer",
497
+ outputSchema: Answer,
498
+ maximumOutputTokens: 64,
499
+ checkpoint: { store: run.store, binding },
500
+ validate: () => Effect.succeed([])
501
+ }).pipe(Effect.provide(run.layer));
502
+ assert.equal((await Effect.runPromise(invoke({ commit: "a" }))).value.answer, "response-1");
503
+ assert.equal((await Effect.runPromise(invoke({ commit: "a" }))).value.answer, "response-1");
504
+ assert.equal((await Effect.runPromise(invoke({ commit: "b" }))).value.answer, "response-2");
505
+ assert.equal(calls, 2);
506
+ });
507
+ test("protocol helpers: stable digests, progress stage mapping, stage order, and context defaults", () => {
508
+ assert.equal(checkpointDigestV1({ b: 1, a: [{ d: 2, c: 3 }] }), checkpointDigestV1({ a: [{ c: 3, d: 2 }], b: 1 }));
509
+ assert.notEqual(checkpointDigestV1({ a: 1 }), checkpointDigestV1({ a: 2 }));
510
+ assert.deepEqual(PIPELINE_STAGES.map(pipelineStageToProgressStageV1), [
511
+ "replay-qualify",
512
+ "author-specification",
513
+ "oracle-design",
514
+ "valid-solutions",
515
+ "tournament",
516
+ "held-out-tournament",
517
+ "admit"
518
+ ]);
519
+ assert.equal(nextPipelineStageV1("seed"), "spec");
520
+ assert.equal(nextPipelineStageV1("admit"), "done");
521
+ const context = makePipelineCaseContextV1({
522
+ repositoryRoot: "/repo",
523
+ caseId: "case-1",
524
+ caseDirectory: "/repo/.routekit/pipeline/case-1",
525
+ dimension,
526
+ primaryModel: "openai/gpt-6-astra",
527
+ budget: { maxModelCalls: 40, maxWallMs: 3_600_000 },
528
+ reasoningEffort: "medium"
529
+ });
530
+ assert.equal(context.reasoningEffort, "medium");
531
+ assert.equal(context.oracleRepetitions, 2);
532
+ assert.equal(context.foldLimit, 2);
533
+ assert.ok(context.modelPlan.assignments.some((entry) => entry.role === "oracle-critic"));
534
+ assert.ok(context.modelPlan.assignments.every((entry) => entry.reasoningEffort === "high"));
535
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,124 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { assertPipelineCheckpointChainV2, assertPipelineCheckpointReplayV2, decodePipelineCheckpointDescriptorV2, decodePipelineScientificIdentityV2, pipelineCheckpointDescriptorDigestV2, pipelineScientificIdentityDigestV2 } from "../case-pipeline-protocol-v2.js";
4
+ const sha = "a".repeat(64);
5
+ const identity = {
6
+ version: 2,
7
+ repository: {
8
+ repositoryId: "repo-1",
9
+ repository: "velum-labs/routekit",
10
+ commitSha: "b".repeat(40),
11
+ treeSha: "c".repeat(40)
12
+ },
13
+ caseId: "case-1",
14
+ coverageCellId: "cell-1",
15
+ dimension: {
16
+ id: "routing",
17
+ description: "Routing behavior",
18
+ includes: ["explicit behavior"],
19
+ excludes: ["unrelated formatting"]
20
+ },
21
+ source: {
22
+ referenceCommitSha: "d".repeat(40),
23
+ referenceTaskDigest: sha,
24
+ basisDigest: "e".repeat(64)
25
+ },
26
+ modelPlan: [
27
+ {
28
+ role: "seed-author",
29
+ model: "openai/gpt-explicit",
30
+ reasoningEffort: "high",
31
+ maximumCalls: 4,
32
+ maximumOutputTokens: 4096
33
+ }
34
+ ],
35
+ policy: {
36
+ foundryPolicyVersion: "foundry-v2",
37
+ admissionPolicyVersion: "admission-v1",
38
+ repositoryModelDigest: "f".repeat(64),
39
+ oracleRepetitions: 2,
40
+ oracleConcurrency: 2,
41
+ foldLimit: 2
42
+ },
43
+ budget: {
44
+ maximumModelCalls: 16,
45
+ maximumWallTimeMs: 60_000,
46
+ maximumArtifactBytes: 1_048_576
47
+ }
48
+ };
49
+ test("scientific identity is path-independent and rejects runtime coordinates and auto models", () => {
50
+ const parsed = decodePipelineScientificIdentityV2(identity);
51
+ assert.equal(pipelineScientificIdentityDigestV2(parsed), pipelineScientificIdentityDigestV2(structuredClone(parsed)));
52
+ assert.throws(() => decodePipelineScientificIdentityV2({ ...identity, repositoryRoot: "/workspace/a" }));
53
+ assert.throws(() => decodePipelineScientificIdentityV2({
54
+ ...identity,
55
+ modelPlan: [{ ...identity.modelPlan[0], model: "auto" }]
56
+ }));
57
+ assert.throws(() => decodePipelineScientificIdentityV2({
58
+ ...identity,
59
+ modelPlan: [identity.modelPlan[0], identity.modelPlan[0]]
60
+ }));
61
+ });
62
+ test("checkpoint descriptors form a fenced, hash-linked, portable chain", () => {
63
+ const identityDigest = pipelineScientificIdentityDigestV2(decodePipelineScientificIdentityV2(identity));
64
+ const first = decodePipelineCheckpointDescriptorV2({
65
+ version: 2,
66
+ checkpointId: "checkpoint-1",
67
+ identityDigest,
68
+ attemptGeneration: 1,
69
+ sequence: 1,
70
+ kind: "stage",
71
+ stage: "seed",
72
+ parentDigest: identityDigest,
73
+ payloadDigest: sha,
74
+ payloadBytes: 10,
75
+ createdAt: "2026-09-11T00:00:00.000Z"
76
+ });
77
+ const second = decodePipelineCheckpointDescriptorV2({
78
+ version: 2,
79
+ checkpointId: "checkpoint-2",
80
+ identityDigest,
81
+ attemptGeneration: 2,
82
+ sequence: 2,
83
+ kind: "loop",
84
+ stage: "oracle",
85
+ loopName: "oracle-critique",
86
+ loopIteration: 0,
87
+ parentDigest: pipelineCheckpointDescriptorDigestV2(first),
88
+ payloadDigest: "9".repeat(64),
89
+ payloadBytes: 20,
90
+ createdAt: "2026-09-11T00:01:00.000Z"
91
+ });
92
+ assert.doesNotThrow(() => assertPipelineCheckpointChainV2(identity, [first, second]));
93
+ assert.throws(() => assertPipelineCheckpointChainV2(identity, [first, { ...second, sequence: 3 }]), /contiguous/);
94
+ assert.throws(() => assertPipelineCheckpointChainV2(identity, [
95
+ first,
96
+ { ...second, parentDigest: "8".repeat(64) }
97
+ ]), /parent/);
98
+ });
99
+ test("checkpoint replay acknowledges exact values and rejects conflicts", () => {
100
+ const identityDigest = pipelineScientificIdentityDigestV2(decodePipelineScientificIdentityV2(identity));
101
+ const checkpoint = decodePipelineCheckpointDescriptorV2({
102
+ version: 2,
103
+ checkpointId: "checkpoint-1",
104
+ identityDigest,
105
+ attemptGeneration: 1,
106
+ sequence: 1,
107
+ kind: "terminal",
108
+ stage: "admit",
109
+ parentDigest: identityDigest,
110
+ payloadDigest: sha,
111
+ payloadBytes: 10,
112
+ createdAt: "2026-09-11T00:00:00.000Z"
113
+ });
114
+ assert.doesNotThrow(() => assertPipelineCheckpointReplayV2(checkpoint, checkpoint));
115
+ assert.throws(() => assertPipelineCheckpointReplayV2(checkpoint, {
116
+ ...checkpoint,
117
+ payloadDigest: "9".repeat(64)
118
+ }), /conflicts/);
119
+ assert.throws(() => decodePipelineCheckpointDescriptorV2({
120
+ ...checkpoint,
121
+ kind: "loop",
122
+ loopName: "missing-iteration"
123
+ }));
124
+ });
@@ -0,0 +1 @@
1
+ export {};