@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,848 @@
1
+ import assert from "node:assert/strict";
2
+ import { execFile } from "node:child_process";
3
+ import { randomUUID } from "node:crypto";
4
+ import { lstat, mkdir, mkdtemp, readFile, realpath, rm, writeFile } from "node:fs/promises";
5
+ import { createServer } from "node:http";
6
+ import { tmpdir } from "node:os";
7
+ import { basename, dirname, isAbsolute, join } from "node:path";
8
+ import { test } from "node:test";
9
+ import { setTimeout as delay } from "node:timers/promises";
10
+ import { promisify, stripVTControlCharacters } from "node:util";
11
+ import { Effect } from "effect";
12
+ import { parse, stringify } from "yaml";
13
+ import { repositoryCommandTestEvidenceV1 } from "../adapters/repository-command-evidence.js";
14
+ import { makeRepositoryPreparationCacheV1, RepositoryReadOnlyPnpmStore, replayRepositoryCommandsV1 } from "../adapters/repository-command-runner.js";
15
+ import { RepositoryFoundryError } from "../errors.js";
16
+ import { REPOSITORY_RESOURCE_CASES, REPOSITORY_RESOURCE_FILES, RESOURCE_BODY_DELAY_MS, RESOURCE_FRAMEWORK_TIMEOUT_MS, RESOURCE_OUTPUT_CAP_BYTES, RESOURCE_OUTPUT_CHUNK_BYTES, RESOURCE_PARENT_DEADLINE_MS } from "./fixtures/repository-resource-panel.js";
17
+ import { VITEST_BOUNDARY_CASES, VITEST_BOUNDARY_FILES, VITEST_COLLECTION_FAILURE_CASES } from "./fixtures/vitest-boundary-panel.js";
18
+ import { VITEST_PHASE_CASES, VITEST_PHASE_CUSTOM_RUNNER_SOURCE, VITEST_PHASE_READER_SOURCE, VITEST_PHASE_REGISTER_SOURCE, VITEST_PHASE_STOCK_READER_SOURCE, VITEST_PHASE_TIMEOUT_CASES, VITEST_PHASE_VERSIONS_SOURCE } from "./fixtures/vitest-phase-panel.js";
19
+ const exec = promisify(execFile);
20
+ const reportPrefix = "ROUTEKIT_VITEST_PHASE_EVIDENCE_V1 ";
21
+ const controlPrefix = "ROUTEKIT_PHASE_CAUSAL_CONTROL ";
22
+ const stockPrefix = "ROUTEKIT_STOCK_VITEST_CONTROL ";
23
+ function oneFrame(output, prefix) {
24
+ const frames = stripVTControlCharacters(output).split("\n").filter(line => line.startsWith(prefix));
25
+ assert.equal(frames.length, 1, `one real ${prefix.trim()} frame is required: ${output}`);
26
+ return JSON.parse(frames[0].slice(prefix.length));
27
+ }
28
+ function completed(result) {
29
+ assert.equal(result.stage, "grade");
30
+ assert.equal(result.gradeExecuted, true, JSON.stringify(result));
31
+ assert.equal(result.timedOut, false, JSON.stringify(result));
32
+ assert.equal(result.outputTruncated, false, JSON.stringify(result));
33
+ assert.notEqual(result.exitCode, null, JSON.stringify(result));
34
+ const preparation = result.preparation?.sharedResults ?? result.trustedPreparationResults;
35
+ assert.equal(preparation.length, 1, "the declared offline dependency preparation must execute");
36
+ for (const item of preparation)
37
+ assert.equal(item.exitCode, 0, JSON.stringify(item));
38
+ assert.ok(result.candidateValidationResults.length > 0);
39
+ for (const item of result.candidateValidationResults)
40
+ assert.equal(item.exitCode, 0, JSON.stringify(item));
41
+ }
42
+ async function fixture(t, additionalFiles = {}) {
43
+ // Trap even metadata-only registry requests: pnpm's tarball --offline flag
44
+ // alone does not keep its lockfile supply-chain verifier off the network.
45
+ const registryRequests = [];
46
+ const registry = createServer((request, response) => {
47
+ registryRequests.push(request.url ?? "");
48
+ response.writeHead(200, { "content-type": "application/json" });
49
+ response.end("{}");
50
+ });
51
+ await new Promise((resolve, reject) => {
52
+ registry.once("error", reject);
53
+ registry.listen(0, "127.0.0.1", resolve);
54
+ });
55
+ t.after(async () => {
56
+ await new Promise((resolve, reject) => registry.close(error => error ? reject(error) : resolve()));
57
+ assert.deepEqual(registryRequests, [], "offline fixture preparation must never query a registry");
58
+ });
59
+ const address = registry.address();
60
+ assert.ok(address !== null && typeof address !== "string");
61
+ const root = await realpath(await mkdtemp(join(tmpdir(), "vitest-phase-regression-")));
62
+ let passed = false;
63
+ t.after(async () => {
64
+ if (passed)
65
+ await rm(root, { recursive: true, force: true });
66
+ else
67
+ t.diagnostic(`Retained failed real-Vitest fixture and artifacts: ${root}`);
68
+ });
69
+ await mkdir(join(root, "artifacts"));
70
+ const record = (name, value) => writeFile(join(root, "artifacts", name), `${JSON.stringify(value, null, 2)}\n`);
71
+ const complete = () => { passed = true; };
72
+ const runId = randomUUID();
73
+ const prehydratedStore = await realpath((await exec("corepack", ["pnpm@11.15.1", "store", "path", "--silent"])).stdout.trim());
74
+ await record("identity.json", { runId, root, node: process.version, platform: process.platform });
75
+ const workspace = parse(await readFile(new URL("../../../../pnpm-workspace.yaml", import.meta.url), "utf8"));
76
+ const lock = parse(await readFile(new URL("../../../../pnpm-lock.yaml", import.meta.url), "utf8"));
77
+ const owner = lock.importers["packages/eval-setup"]?.devDependencies;
78
+ assert.equal(workspace.catalog.vitest, "3.2.7");
79
+ assert.equal(workspace.catalog.vite, "7.3.6");
80
+ assert.ok(owner?.vitest, "Vitest must be declared and installed by this repository, not found in another checkout");
81
+ assert.ok(owner.vite, "Vite must be declared in the root catalog and eval-setup dev dependencies");
82
+ assert.equal(owner.vitest.specifier, "catalog:");
83
+ assert.equal(owner.vite.specifier, "catalog:");
84
+ // Derive the exact frozen dependency graph from the owning workspace lock.
85
+ // This avoids a second drift-prone lock and ensures normal root installation
86
+ // fetches the very package graph later used by this offline regression.
87
+ const fixtureLock = {
88
+ ...lock,
89
+ importers: { ".": { devDependencies: { vite: owner.vite, vitest: owner.vitest } } }
90
+ };
91
+ const workspaceConfig = {
92
+ packages: [],
93
+ catalog: workspace.catalog,
94
+ ...(workspace.catalogs === undefined ? {} : { catalogs: workspace.catalogs }),
95
+ ...(workspace.overrides === undefined ? {} : { overrides: workspace.overrides }),
96
+ ...(workspace.minimumReleaseAgeExclude === undefined ? {} : {
97
+ minimumReleaseAgeExclude: workspace.minimumReleaseAgeExclude
98
+ }),
99
+ autoInstallPeers: lock.settings.autoInstallPeers,
100
+ enableGlobalVirtualStore: false,
101
+ frozenStore: true,
102
+ registry: `http://127.0.0.1:${address.port}/`,
103
+ // The root install already checks the repository-owned lock under .npmrc's
104
+ // supply-chain policy and hydrates its integrity-addressed store. This
105
+ // test-only fixture reuses exactly those pins, not a candidate's lockfile.
106
+ // pnpm 11.15.1's verifier ignores --offline and re-fetches registry metadata
107
+ // in the isolated HOME. Its documented trusted-base option avoids that
108
+ // second online policy check without disabling frozen-lockfile, frozen-store,
109
+ // integrity verification, or any production preparation policy.
110
+ trustLockfile: true
111
+ };
112
+ await mkdir(join(root, "test"));
113
+ await mkdir(join(root, "scripts"));
114
+ const files = {
115
+ "package.json": JSON.stringify({ name: "routekit-vitest-phase-regression", private: true, type: "module",
116
+ packageManager: "pnpm@11.15.1", devDependencies: { vite: "catalog:", vitest: "catalog:" } }),
117
+ "pnpm-workspace.yaml": stringify(workspaceConfig),
118
+ // This fixture borrows the root lock, so it must retain the same reviewed
119
+ // package policies rather than rejecting root-approved fresh dependency pins.
120
+ ".npmrc": await readFile(new URL("../../../../.npmrc", import.meta.url), "utf8"),
121
+ "pnpm-lock.yaml": stringify(fixtureLock),
122
+ ".gitignore": "node_modules/\nphases/\ncustom-runner-imported\nartifacts/\nstock-*.json\n",
123
+ "identity.json": JSON.stringify({ runId }),
124
+ "test/register.mjs": VITEST_PHASE_REGISTER_SOURCE,
125
+ "scripts/read-phases.mjs": VITEST_PHASE_READER_SOURCE,
126
+ "scripts/read-stock.mjs": VITEST_PHASE_STOCK_READER_SOURCE,
127
+ "scripts/versions.mjs": VITEST_PHASE_VERSIONS_SOURCE,
128
+ "custom-runner.mjs": VITEST_PHASE_CUSTOM_RUNNER_SOURCE,
129
+ "scripts/unsupported-version.mjs": String.raw `
130
+ import assert from "node:assert/strict";
131
+ import { readFile, realpath, rename, writeFile } from "node:fs/promises";
132
+ import { createRequire } from "node:module";
133
+ import { isAbsolute, relative, sep } from "node:path";
134
+ const require = createRequire(import.meta.url);
135
+ const path = await realpath(require.resolve("vitest/package.json"));
136
+ const local = relative(await realpath(process.cwd()), path);
137
+ assert.equal(local === ".." || local.startsWith(".." + sep) || isAbsolute(local), false);
138
+ const manifest = JSON.parse(await readFile(path, "utf8"));
139
+ assert.equal(manifest.version, "3.2.7");
140
+ // Replace the isolated directory entry; never write a potentially shared inode.
141
+ const temporary = path + ".unsupported-probe";
142
+ await writeFile(temporary, JSON.stringify({ ...manifest, version: "0.0.0-phase-unsupported" }), { flag: "wx" });
143
+ await rename(temporary, path);
144
+ console.log("ROUTEKIT_PHASE_ISOLATED_VERSION_CONTROL");
145
+ `
146
+ };
147
+ for (const scenario of [...VITEST_PHASE_CASES, ...VITEST_PHASE_TIMEOUT_CASES])
148
+ files[`test/${scenario.id}.test.mjs`] =
149
+ `import { register } from "./register.mjs";\nregister(${JSON.stringify(scenario)});\n`;
150
+ for (const [path, content] of Object.entries(additionalFiles)) {
151
+ assert.equal(Object.hasOwn(files, path), false, "boundary fixtures cannot replace the original phase fixture files");
152
+ files[path] = content;
153
+ }
154
+ for (const [path, source] of Object.entries(files))
155
+ await writeFile(join(root, path), source);
156
+ const git = (...args) => exec("git", ["-C", root, ...args]);
157
+ await git("init", "-q");
158
+ await git("add", ".");
159
+ await git("-c", "core.hooksPath=/dev/null", "-c", "commit.gpgsign=false", "-c", "user.name=Fixture", "-c", "user.email=fixture@example.test", "commit", "-qm", `phase regression ${runId}`);
160
+ const commit = (await git("rev-parse", "HEAD")).stdout.trim();
161
+ const preparation = [{ id: "offline-frozen-install", kind: "custom", cwd: ".",
162
+ executable: "corepack", args: ["pnpm@11.15.1", "install", "--offline", "--frozen-lockfile",
163
+ "--ignore-pnpmfile", "--ignore-scripts"], timeoutMs: 60000 }];
164
+ const validation = [{ id: "pinned-runtime-versions", kind: "check", cwd: ".",
165
+ executable: "node", args: ["scripts/versions.mjs"], timeoutMs: 10000 }];
166
+ const grade = (id) => {
167
+ const timeout = VITEST_PHASE_TIMEOUT_CASES.find(scenario => scenario.id === id)?.hookTimeoutMs;
168
+ return { id: `grade-${id}`, kind: "test", cwd: ".",
169
+ executable: "corepack", args: ["pnpm@11.15.1", "exec", "vitest", "run", `test/${id}.test.mjs`,
170
+ "--pool=forks", "--maxWorkers=1", "--minWorkers=1", "--no-file-parallelism",
171
+ ...(timeout === undefined ? [] : [`--hookTimeout=${timeout}`])], timeoutMs: 60000 };
172
+ };
173
+ const control = (id) => ({ id: `control-${id}`, kind: "custom", cwd: ".",
174
+ executable: "node", args: ["scripts/read-phases.mjs", id], timeoutMs: 10000 });
175
+ const stock = (id) => {
176
+ const base = grade(id);
177
+ return { ...base, id: `stock-${id}`, kind: "custom",
178
+ // Deliberately not a host-recognized Vitest grade command: this is the
179
+ // pinned framework's ordinary lifecycle, with its stock JSON reporter.
180
+ args: [base.args[0], "exec", "node", "node_modules/vitest/vitest.mjs", ...base.args.slice(3),
181
+ "--api.host=127.0.0.1", "--api.middlewareMode", "--reporter=json", `--outputFile=stock-${id}.json`] };
182
+ };
183
+ const stockControl = (id) => ({ ...control(id),
184
+ id: `stock-control-${id}`, args: ["scripts/read-stock.mjs", id] });
185
+ await record("fixture.json", { runId, commit, pins: { vitest: workspace.catalog.vitest, vite: workspace.catalog.vite },
186
+ preparation, validation });
187
+ return { root, runId, commit, preparation, validation, grade, control, stock, stockControl,
188
+ record, complete, signal: t.signal, prehydratedStore };
189
+ }
190
+ async function executeRecorded(fixture, name, program) {
191
+ let effectCause;
192
+ try {
193
+ // The root frozen install hydrates this store before the offline test starts.
194
+ // Explicitly borrow it read-only; production continues to use its private store.
195
+ const configured = fixture.prehydratedStore === undefined ? program : program.pipe(Effect.provideService(RepositoryReadOnlyPnpmStore, fixture.prehydratedStore));
196
+ const results = await Effect.runPromise(Effect.onError(configured, cause => Effect.sync(() => {
197
+ // Effect 4 runPromise squashes the Cause before rejecting. Preserve its
198
+ // complete structure here without changing the caller's rejection.
199
+ effectCause = cause;
200
+ })), { signal: fixture.signal });
201
+ await fixture.record(`${name}-results.json`, results);
202
+ return results;
203
+ }
204
+ catch (error) {
205
+ const details = (root) => {
206
+ const seen = new WeakMap();
207
+ const encode = (value, path) => {
208
+ if (value === undefined)
209
+ return { $undefined: true };
210
+ if (typeof value === "bigint")
211
+ return { $bigint: String(value) };
212
+ if (typeof value === "symbol")
213
+ return { $symbol: String(value) };
214
+ if (typeof value === "function")
215
+ return { $function: value.name };
216
+ if (value === null || typeof value !== "object")
217
+ return value;
218
+ const previous = seen.get(value);
219
+ if (previous !== undefined)
220
+ return { $ref: previous };
221
+ seen.set(value, path);
222
+ if (Array.isArray(value))
223
+ return value.map((item, index) => encode(item, `${path}[${index}]`));
224
+ if (value instanceof Map)
225
+ return { $map: [...value].map(([key, item], index) => [
226
+ encode(key, `${path}.keys[${index}]`), encode(item, `${path}.values[${index}]`)
227
+ ]) };
228
+ if (value instanceof Set)
229
+ return { $set: [...value].map((item, index) => encode(item, `${path}[${index}]`)) };
230
+ const result = Object.create(null);
231
+ if (value instanceof Error) {
232
+ result.name = value.name;
233
+ result.message = value.message;
234
+ result.stack = value.stack;
235
+ }
236
+ // Preserve nonenumerable Error.cause and the Effect tagged-error
237
+ // fields/symbol payloads. Do not invoke arbitrary toJSON methods.
238
+ for (const key of Reflect.ownKeys(value)) {
239
+ if (value instanceof Error && (key === "name" || key === "message" || key === "stack"))
240
+ continue;
241
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
242
+ const name = typeof key === "symbol" ? `[${String(key)}]` : key;
243
+ result[name] = "value" in descriptor
244
+ ? encode(descriptor.value, `${path}.${name}`)
245
+ : { $accessorNotInvoked: true };
246
+ }
247
+ return result;
248
+ };
249
+ return encode(root, "$");
250
+ };
251
+ await fixture.record(`${name}-error.json`, {
252
+ message: String(error), ...(error instanceof Error ? { stack: error.stack } : {}),
253
+ rejection: details(error), effectCause: details(effectCause)
254
+ });
255
+ throw error;
256
+ }
257
+ }
258
+ test("recorded replay interruption reaches Effect finalizers", { timeout: 5000 }, async () => {
259
+ const controller = new AbortController();
260
+ const records = new Map();
261
+ let finalized = false;
262
+ let entered = () => { };
263
+ const started = new Promise(resolve => { entered = resolve; });
264
+ const pending = executeRecorded({
265
+ signal: controller.signal,
266
+ record: async (name, value) => { records.set(name, JSON.parse(JSON.stringify(value))); }
267
+ }, "cancelled", Effect.ensuring(Effect.andThen(Effect.sync(entered), Effect.never), Effect.sync(() => { finalized = true; })));
268
+ const rejected = assert.rejects(pending);
269
+ await started;
270
+ controller.abort();
271
+ await rejected;
272
+ assert.equal(finalized, true);
273
+ const record = records.get("cancelled-error.json");
274
+ assert.ok(record.effectCause.reasons.some(reason => reason._tag === "Interrupt"));
275
+ });
276
+ test("recorded replay failure retains pristine preparation output and nested error causes", async (t) => {
277
+ const records = new Map();
278
+ const error = new RepositoryFoundryError({
279
+ operation: "prepare-checkout", detail: "offline preparation failed",
280
+ cause: { templateKey: "fixture-template", commit: "fixture-commit",
281
+ trustedPreparationResults: [{ exitCode: 1, stdout: "complete preparation stdout", stderr: "offline metadata unavailable" }],
282
+ nested: new Error("nested preparation error", { cause: { stage: "frozen-offline-install" } }) }
283
+ });
284
+ await assert.rejects(executeRecorded({
285
+ signal: t.signal, record: async (name, value) => { records.set(name, JSON.parse(JSON.stringify(value))); }
286
+ }, "preparation", Effect.fail(error)), rejected => rejected === error);
287
+ const record = records.get("preparation-error.json");
288
+ assert.equal(record.rejection.name, "RepositoryFoundryError");
289
+ assert.equal(record.rejection.message, error.message);
290
+ assert.equal(record.rejection.operation, error.operation);
291
+ assert.equal(record.rejection.detail, error.detail);
292
+ assert.equal(record.rejection.cause.templateKey, "fixture-template");
293
+ assert.equal(record.rejection.cause.commit, "fixture-commit");
294
+ assert.deepEqual(record.rejection.cause.trustedPreparationResults, [
295
+ { exitCode: 1, stdout: "complete preparation stdout", stderr: "offline metadata unavailable" }
296
+ ]);
297
+ assert.equal(record.rejection.cause.nested.name, "Error");
298
+ assert.equal(record.rejection.cause.nested.message, "nested preparation error");
299
+ assert.ok(record.rejection.cause.nested.stack.includes("nested preparation error"));
300
+ assert.deepEqual(record.rejection.cause.nested.cause, { stage: "frozen-offline-install" });
301
+ assert.equal(record.effectCause.reasons[0]?._tag, "Fail");
302
+ const causeError = record.effectCause.reasons[0]?.error;
303
+ assert.equal(causeError.name, record.rejection.name);
304
+ assert.equal(causeError.message, record.rejection.message);
305
+ assert.equal(causeError.operation, record.rejection.operation);
306
+ assert.equal(causeError.detail, record.rejection.detail);
307
+ assert.deepEqual(causeError.cause, record.rejection.cause);
308
+ });
309
+ test("offline pinned preparation fails closed when the prehydrated store is empty", { timeout: 60000 }, async (t) => {
310
+ const f = await fixture(t);
311
+ const emptyStore = join(f.root, "empty-store");
312
+ const emptyProject = join(f.root, "empty-project");
313
+ await mkdir(emptyProject);
314
+ await writeFile(join(emptyProject, "package.json"), JSON.stringify({
315
+ name: "empty-offline-store", private: true, packageManager: "pnpm@11.15.1"
316
+ }));
317
+ // Initialize pnpm's real store schema without hydrating any packages. An
318
+ // absent database would only test SQLite opening, not offline dependency refusal.
319
+ await exec("corepack", ["pnpm@11.15.1", "install", "--offline", "--ignore-workspace",
320
+ "--ignore-scripts", "--store-dir", emptyStore], { cwd: emptyProject });
321
+ const versionedStore = (await exec("corepack", ["pnpm@11.15.1", "store", "path", "--silent",
322
+ "--store-dir", emptyStore], { cwd: emptyProject })).stdout.trim();
323
+ const results = await executeRecorded({ ...f, prehydratedStore: versionedStore }, "missing-offline-dependencies", replayRepositoryCommandsV1({
324
+ repositoryRoot: f.root, commit: f.commit, trustedPreparationRecipes: f.preparation,
325
+ candidateValidationRecipes: f.validation, recipes: [f.grade("positive")], repetitions: 1
326
+ }));
327
+ assert.equal(results.length, 1);
328
+ const result = results[0];
329
+ assert.equal(result.stage, "trusted-preparation");
330
+ assert.equal(result.gradeExecuted, false);
331
+ assert.equal(result.timedOut, false, JSON.stringify(result));
332
+ assert.equal(result.candidateValidationResults.length, 0);
333
+ assert.equal(result.trustedPreparationResults.length, 1);
334
+ const preparation = result.trustedPreparationResults[0];
335
+ assert.notEqual(preparation.exitCode, 0);
336
+ assert.notEqual(preparation.exitCode, null);
337
+ assert.match(`${preparation.stdout}\n${preparation.stderr}`, /ERR_PNPM_(?:NO_OFFLINE|FROZEN_STORE)/u);
338
+ f.complete();
339
+ });
340
+ test("real pinned runtime validation refuses missing or redirected fixture-local launchers", { timeout: 120000 }, async (t) => {
341
+ const f = await fixture(t);
342
+ for (const { name, kind } of [
343
+ { name: "vite", kind: "missing" },
344
+ { name: "vitest", kind: "missing" },
345
+ { name: "vitest", kind: "redirected" }
346
+ ]) {
347
+ const alterLauncher = {
348
+ id: `${kind}-${name}-launcher`, kind: "check", cwd: ".", executable: "node",
349
+ args: ["-e", `
350
+ const { rmSync, writeFileSync } = require("node:fs");
351
+ const { join } = require("node:path");
352
+ const launcher = join("node_modules", ".bin", process.argv[1] +
353
+ (process.platform === "win32" ? ".cmd" : ""));
354
+ rmSync(launcher);
355
+ if (process.argv[2] === "redirected") {
356
+ writeFileSync("scripts/redirected-runtime.mjs", 'console.log("vitest/3.2.7 fixture");\\n', { flag: "wx" });
357
+ writeFileSync(launcher, process.platform === "win32"
358
+ ? "@echo off\\r\\nnode scripts\\\\redirected-runtime.mjs %*\\r\\n"
359
+ : '#!/bin/sh\\nexec node scripts/redirected-runtime.mjs "$@"\\n', { flag: "wx", mode: 0o700 });
360
+ }
361
+ `, name, kind],
362
+ timeoutMs: 10000
363
+ };
364
+ const results = await executeRecorded(f, `${kind}-${name}-launcher`, replayRepositoryCommandsV1({
365
+ repositoryRoot: f.root, commit: f.commit, trustedPreparationRecipes: f.preparation,
366
+ candidateValidationRecipes: [alterLauncher, ...f.validation],
367
+ recipes: [f.grade("positive")], repetitions: 1
368
+ }));
369
+ assert.equal(results.length, 1);
370
+ const result = results[0];
371
+ assert.equal(result.stage, "candidate-validation");
372
+ assert.equal(result.gradeExecuted, false, "an invalid local launcher must fail before any grade runs");
373
+ assert.equal(result.timedOut, false);
374
+ assert.equal(result.outputTruncated, false);
375
+ assert.equal(result.exitCode, 1);
376
+ assert.equal(result.trustedPreparationResults.length, 1);
377
+ assert.equal(result.trustedPreparationResults[0].exitCode, 0);
378
+ assert.equal(result.candidateValidationResults.length, 2);
379
+ assert.equal(result.candidateValidationResults[0].exitCode, 0, "the frozen install must create the local launcher before this negative control changes it");
380
+ if (kind === "missing")
381
+ assert.match(result.stderr, /ENOENT/u);
382
+ else
383
+ assert.match(result.stderr, /vitest must execute its checkout-local pinned entrypoint/u, "an executable local shim and matching version text cannot replace actual entrypoint proof");
384
+ assert.equal(result.stdout.includes("ROUTEKIT_PINNED_VITEST_RUNTIME"), false);
385
+ assert.equal(`${result.stdout}\n${result.stderr}`.includes(reportPrefix), false);
386
+ }
387
+ f.complete();
388
+ });
389
+ test("real pinned Vitest preserves body signal and refuses cleanup or unobserved phases", { timeout: 240000 }, async (t) => {
390
+ const f = await fixture(t);
391
+ const results = await executeRecorded(f, "phase-panel", Effect.scoped(Effect.gen(function* () {
392
+ const cache = process.platform === "darwin" ? yield* makeRepositoryPreparationCacheV1({
393
+ repositoryRoot: f.root, commits: [f.commit], trustedPreparationRecipes: f.preparation
394
+ }) : undefined;
395
+ return yield* replayRepositoryCommandsV1({ repositoryRoot: f.root, commit: f.commit,
396
+ trustedPreparationRecipes: f.preparation, candidateValidationRecipes: f.validation,
397
+ ...(cache === undefined ? {} : { preparationCache: cache }),
398
+ recipes: VITEST_PHASE_CASES.flatMap(scenario => [f.grade(scenario.id), f.control(scenario.id)]),
399
+ repetitions: 2 });
400
+ })));
401
+ assert.equal(results.length, VITEST_PHASE_CASES.length * 4, "all real grade and causal-control subprocesses must finish");
402
+ for (const repetition of [1, 2])
403
+ for (const scenario of VITEST_PHASE_CASES) {
404
+ const result = results.find(item => item.repetition === repetition && item.recipeId === f.grade(scenario.id).id);
405
+ const controlResult = results.find(item => item.repetition === repetition && item.recipeId === f.control(scenario.id).id);
406
+ assert.ok(result);
407
+ assert.ok(controlResult);
408
+ completed(result);
409
+ completed(controlResult);
410
+ assert.equal(controlResult.exitCode, 0, JSON.stringify(controlResult));
411
+ const control = oneFrame(controlResult.stdout, controlPrefix);
412
+ assert.equal(control.runId, f.runId);
413
+ assert.equal(control.scenario, scenario.id);
414
+ assert.equal(control.customRunnerImported, false);
415
+ assert.deepEqual(control.events.map(event => event.phase), scenario.events, `${scenario.id} repetition ${repetition}`);
416
+ for (const [sequence, event] of control.events.entries()) {
417
+ assert.equal(event.sequence, sequence);
418
+ assert.equal(event.runId, f.runId);
419
+ assert.equal(event.scenario, scenario.id);
420
+ }
421
+ const report = oneFrame(`${result.stdout}\n${result.stderr}`, reportPrefix);
422
+ assert.equal(report.version, 1);
423
+ assert.equal(report.complete, true, JSON.stringify(report));
424
+ assert.equal(report.starts, 1);
425
+ assert.equal(report.ends, 1);
426
+ assert.equal(report.eventIntegrity, true);
427
+ if (scenario.expected === "unknown") {
428
+ assert.deepEqual(report.unhandledErrors, [{ name: "RouteKitVitestPhaseAttributionError",
429
+ code: "ROUTEKIT_VITEST_PHASE_UNATTRIBUTED" }], "unknown must be the intended host attribution refusal, not a missing dependency/import/launch failure");
430
+ }
431
+ else
432
+ assert.deepEqual(report.unhandledErrors, []);
433
+ assert.equal(result.exitCode, scenario.expected === "pass" ? 0 : 1, JSON.stringify(result));
434
+ assert.equal(repositoryCommandTestEvidenceV1(result, result.recipeId).outcome, scenario.expected, `${scenario.id} repetition ${repetition}: ${JSON.stringify(result)}`);
435
+ }
436
+ f.complete();
437
+ });
438
+ function boundaryControl(result, runId, scenario) {
439
+ const control = oneFrame(result.stdout, controlPrefix);
440
+ assert.equal(control.runId, runId);
441
+ assert.equal(control.scenario, scenario.id);
442
+ assert.equal(control.customRunnerImported, false);
443
+ assert.deepEqual(control.events.map(event => event.phase), scenario.events);
444
+ for (const [sequence, event] of control.events.entries()) {
445
+ assert.equal(event.sequence, sequence);
446
+ assert.equal(event.runId, runId);
447
+ assert.equal(event.scenario, scenario.id);
448
+ assert.ok(Number.isInteger(event.process.pid) && event.process.pid > 1);
449
+ assert.equal(typeof event.process.checkout, "string");
450
+ }
451
+ return control;
452
+ }
453
+ test("real pinned Vitest preserves direct hooks and native collection-failure identities", { timeout: 240000 }, async (t) => {
454
+ const f = await fixture(t, VITEST_BOUNDARY_FILES);
455
+ const replay = (stock) => Effect.scoped(Effect.gen(function* () {
456
+ const cache = process.platform === "darwin" ? yield* makeRepositoryPreparationCacheV1({
457
+ repositoryRoot: f.root, commits: [f.commit], trustedPreparationRecipes: f.preparation
458
+ }) : undefined;
459
+ const scenarios = stock ? VITEST_COLLECTION_FAILURE_CASES : VITEST_BOUNDARY_CASES;
460
+ return yield* replayRepositoryCommandsV1({ repositoryRoot: f.root, commit: f.commit,
461
+ trustedPreparationRecipes: f.preparation, candidateValidationRecipes: f.validation,
462
+ ...(cache === undefined ? {} : { preparationCache: cache }),
463
+ recipes: scenarios.flatMap(scenario => stock
464
+ ? [f.stock(scenario.id), f.stockControl(scenario.id)]
465
+ : [f.grade(scenario.id), f.control(scenario.id)]),
466
+ repetitions: 2 });
467
+ }));
468
+ // Preserve original/stock diagnostics independently before testing the host
469
+ // adapter. A host crash is not an acceptable substitute for native refusal.
470
+ const baseline = await executeRecorded(f, "boundary-stock-collection", replay(true));
471
+ const phased = await executeRecorded(f, "boundary-phase-panel", replay(false));
472
+ assert.equal(baseline.length, VITEST_COLLECTION_FAILURE_CASES.length * 4);
473
+ assert.equal(phased.length, VITEST_BOUNDARY_CASES.length * 4);
474
+ for (const repetition of [1, 2])
475
+ for (const scenario of VITEST_BOUNDARY_CASES) {
476
+ const result = phased.find(item => item.repetition === repetition && item.recipeId === f.grade(scenario.id).id);
477
+ const controlResult = phased.find(item => item.repetition === repetition && item.recipeId === f.control(scenario.id).id);
478
+ assert.ok(result);
479
+ assert.ok(controlResult);
480
+ completed(result);
481
+ completed(controlResult);
482
+ assert.equal(controlResult.exitCode, 0);
483
+ const control = boundaryControl(controlResult, f.runId, scenario);
484
+ const report = oneFrame(`${result.stdout}\n${result.stderr}`, reportPrefix);
485
+ assert.equal(report.version, 1);
486
+ assert.equal(report.complete, true, JSON.stringify(report));
487
+ assert.equal(report.starts, 1);
488
+ assert.equal(report.ends, 1);
489
+ assert.equal(report.eventIntegrity, true);
490
+ assert.equal(report.modules.length, 1);
491
+ const module = report.modules[0];
492
+ assert.equal(module.tests.length, scenario.taskState === "none" ? 0 : 1);
493
+ // Hook refusal interrupts Vitest before its final throttled task-update
494
+ // flush. The causal journal and exact refusal below remain authoritative;
495
+ // the parent task snapshot can still be pending or reflect a passed body.
496
+ if (scenario.taskState !== "none" && !(scenario.kind === "hooks" && scenario.expected === "unknown")) {
497
+ assert.equal(module.tests[0].state, scenario.taskState);
498
+ }
499
+ if (scenario.kind === "import-error" || scenario.kind === "syntax-error") {
500
+ const stock = baseline.find(item => item.repetition === repetition && item.recipeId === f.stock(scenario.id).id);
501
+ const stockControl = baseline.find(item => item.repetition === repetition && item.recipeId === f.stockControl(scenario.id).id);
502
+ assert.ok(stock);
503
+ assert.ok(stockControl);
504
+ completed(stock);
505
+ completed(stockControl);
506
+ assert.equal(stock.exitCode, 1);
507
+ assert.equal(stockControl.exitCode, 0);
508
+ assert.equal(stock.testReporter, undefined);
509
+ assert.equal(`${stock.stdout}\n${stock.stderr}`.includes(reportPrefix), false);
510
+ const original = boundaryControl(stockControl, f.runId, scenario);
511
+ const stockReport = oneFrame(stockControl.stdout, stockPrefix);
512
+ assert.equal(stockReport.runId, f.runId);
513
+ assert.equal(stockReport.scenario, scenario.id);
514
+ assert.equal(stockReport.report.success, false);
515
+ assert.equal(stockReport.report.numTotalTests, 0);
516
+ assert.equal(stockReport.report.testResults.length, 1);
517
+ assert.equal(stockReport.report.testResults[0].status, "failed");
518
+ const diagnostics = [original, control].map(value => {
519
+ const rejection = value.events.find(event => event.phase === "import-rejected");
520
+ assert.ok(rejection?.diagnostic);
521
+ const detail = rejection.diagnostic;
522
+ if (scenario.kind === "import-error") {
523
+ assert.equal(detail.name, "RouteKitFixtureImportError");
524
+ assert.equal(detail.code, "ROUTEKIT_FIXTURE_IMPORT_FAILURE");
525
+ assert.equal(detail.message, "routekit fixture import failed before test registration");
526
+ }
527
+ else {
528
+ assert.match(detail.message, /Parse failure:|Failed to parse source for import analysis/u);
529
+ assert.ok(`${detail.id ?? ""}\n${detail.message}`.includes("edge-import-syntax-bad.mjs"));
530
+ }
531
+ return { name: detail.name, ...(detail.code === undefined ? {} : { code: detail.code }),
532
+ message: detail.message.replaceAll(rejection.process.checkout, "$CHECKOUT") };
533
+ });
534
+ assert.deepEqual(diagnostics[1], diagnostics[0], "instrumentation must preserve the native import/syntax diagnostic");
535
+ const native = diagnostics[0];
536
+ const nativeIdentity = { name: native.name,
537
+ ...(native.code === undefined ? {} : { code: native.code }) };
538
+ assert.deepEqual(module.errors, [nativeIdentity], "the original collection failure must remain the file error");
539
+ }
540
+ assert.deepEqual(report.unhandledErrors, scenario.expected === "unknown" ? [
541
+ { name: "RouteKitVitestPhaseAttributionError", code: "ROUTEKIT_VITEST_PHASE_UNATTRIBUTED" }
542
+ ] : [], "a host TypeError or arbitrary crash does not satisfy phase refusal");
543
+ assert.equal(result.exitCode, scenario.expected === "pass" ? 0 : 1, JSON.stringify(result));
544
+ assert.equal(repositoryCommandTestEvidenceV1(result, result.recipeId).outcome, scenario.expected);
545
+ }
546
+ f.complete();
547
+ });
548
+ function workerDeathIpcDiagnostic(result) {
549
+ const output = stripVTControlCharacters(`${result.stdout}\n${result.stderr}`);
550
+ const lines = output.split("\n").map(line => line.trim());
551
+ const primaryLines = lines.filter(line => /^(?:[A-Za-z_$][\w$]*Error|Error): /u.test(line));
552
+ assert.deepEqual(primaryLines, ["Error: Channel closed"]);
553
+ const primary = primaryLines[0].match(/^(Error): (Channel closed)$/u);
554
+ const serialized = lines.filter(line => line.startsWith("Serialized Error: "));
555
+ assert.deepEqual(serialized, ["Serialized Error: { code: 'ERR_IPC_CHANNEL_CLOSED' }"]);
556
+ const code = serialized[0].match(/code: '([^']+)'/u)[1];
557
+ const index = lines.indexOf(primaryLines[0]);
558
+ assert.match(lines[index + 1], /target\.send node:internal\/child_process:\d+:\d+$/u);
559
+ assert.match(lines[index + 2], /ProcessWorker\.send .*tinypool\/dist\/index\.js:\d+:\d+$/u);
560
+ assert.match(lines[index + 3], /MessagePort\.<anonymous> .*tinypool\/dist\/index\.js:\d+:\d+$/u);
561
+ assert.match(output, /Unhandled Rejection/u);
562
+ assert.equal(output.includes(reportPrefix), false, "the demonstrated native fatal IPC path exits before parent run-end reporting");
563
+ return { name: primary[1], message: primary[2], code };
564
+ }
565
+ async function disposedResourceWorker(control, fixtureRoot, signal) {
566
+ const entered = control.events.find(event => event.phase === "body-enter");
567
+ assert.ok(entered, "the actual grading worker must enter the body before the failure boundary");
568
+ const observed = entered.process;
569
+ assert.ok(Number.isSafeInteger(observed.pid) && observed.pid > 1);
570
+ assert.notEqual(observed.pid, process.pid, "only a recorded fresh fixture-owned process may be probed");
571
+ assert.ok(Number.isSafeInteger(observed.parentPid) && observed.parentPid > 1);
572
+ assert.notEqual(observed.checkout, fixtureRoot);
573
+ assert.equal(isAbsolute(observed.checkout), true);
574
+ const isolationRoot = dirname(observed.checkout);
575
+ assert.match(basename(isolationRoot), /^routekit-foundry-isolated-/u);
576
+ assert.equal(observed.checkout, join(isolationRoot, "repository"));
577
+ assert.equal(observed.home, join(isolationRoot, "home"));
578
+ assert.equal(observed.temporary, join(isolationRoot, "tmp"));
579
+ for (const event of control.events)
580
+ assert.deepEqual(event.process, observed);
581
+ const paths = [observed.checkout, observed.home, observed.temporary, isolationRoot];
582
+ // Replay's scope has already resolved. Probe only paths recorded by the
583
+ // fresh fixture; do not scan temporary directories or add cleanup fallbacks.
584
+ for (const path of paths)
585
+ await assert.rejects(lstat(path), error => error instanceof Error && "code" in error && error.code === "ENOENT", `runner scope must dispose ${path}`);
586
+ for (let attempt = 0; attempt < 100; attempt += 1) {
587
+ try {
588
+ process.kill(observed.pid, 0);
589
+ }
590
+ catch (error) {
591
+ assert.ok(error instanceof Error && "code" in error);
592
+ assert.equal(error.code, "ESRCH", "EPERM or arbitrary signal errors do not prove process disposal");
593
+ return { pid: observed.pid, paths, processCode: error.code, pathCode: "ENOENT" };
594
+ }
595
+ await delay(50, undefined, { signal });
596
+ }
597
+ assert.fail(`fresh grading worker ${observed.pid} still exists after its runner scope was disposed`);
598
+ }
599
+ test("real repository execution refuses worker death, parent deadlines and bounded output overflow", { timeout: 180000 }, async (t) => {
600
+ assert.ok(RESOURCE_PARENT_DEADLINE_MS < RESOURCE_BODY_DELAY_MS);
601
+ assert.ok(RESOURCE_BODY_DELAY_MS < RESOURCE_FRAMEWORK_TIMEOUT_MS);
602
+ assert.equal(RESOURCE_OUTPUT_CAP_BYTES, 2 * 1024 * 1024);
603
+ const f = await fixture(t, REPOSITORY_RESOURCE_FILES);
604
+ const grade = (scenario) => ({
605
+ ...(scenario.runner === "vitest" ? f.grade(scenario.id) : {
606
+ id: `grade-${scenario.id}`, kind: "test", cwd: ".", executable: "node",
607
+ args: ["--test", `test/${scenario.id}.test.mjs`]
608
+ }),
609
+ timeoutMs: scenario.kind === "deadline" ? RESOURCE_PARENT_DEADLINE_MS : 20000
610
+ });
611
+ const terminated = REPOSITORY_RESOURCE_CASES.find(scenario => scenario.kind === "termination");
612
+ const stock = f.stock(terminated.id);
613
+ const native = {
614
+ ...stock, id: "native-worker-termination", timeoutMs: 20000,
615
+ // Preserve native fatal IPC diagnostics after actual worker termination.
616
+ // It is a causal baseline, not a host-recognized semantic grading recipe.
617
+ args: stock.args.filter(value => !value.startsWith("--outputFile=")).map(value => value === "--reporter=json" ? "--reporter=default" : value)
618
+ };
619
+ const replay = (recipes, continueAfterIncompleteGrade = false) => Effect.scoped(Effect.gen(function* () {
620
+ const cache = process.platform === "darwin" ? yield* makeRepositoryPreparationCacheV1({
621
+ repositoryRoot: f.root, commits: [f.commit], trustedPreparationRecipes: f.preparation
622
+ }) : undefined;
623
+ return yield* replayRepositoryCommandsV1({ repositoryRoot: f.root, commit: f.commit,
624
+ trustedPreparationRecipes: f.preparation, candidateValidationRecipes: f.validation,
625
+ ...(cache === undefined ? {} : { preparationCache: cache }), recipes, repetitions: 2,
626
+ continueAfterIncompleteGrade });
627
+ }));
628
+ const baseline = await executeRecorded(f, "resource-native-worker", replay([native, f.control(terminated.id)]));
629
+ // Unlike qualification, this causal diagnostic must inspect cleanup and run
630
+ // a healthy control after intentionally killing/timing out a worker.
631
+ const results = await executeRecorded(f, "resource-panel", replay(REPOSITORY_RESOURCE_CASES.flatMap(scenario => [grade(scenario), f.control(scenario.id)]), true));
632
+ assert.equal(baseline.length, 4);
633
+ assert.equal(results.length, REPOSITORY_RESOURCE_CASES.length * 4);
634
+ const disposals = [];
635
+ const checkoutByRepetition = new Map();
636
+ for (const repetition of [1, 2]) {
637
+ const nativeResult = baseline.find(item => item.repetition === repetition && item.recipeId === native.id);
638
+ const nativeControl = baseline.find(item => item.repetition === repetition && item.recipeId === f.control(terminated.id).id);
639
+ assert.ok(nativeResult);
640
+ assert.ok(nativeControl);
641
+ completed(nativeResult);
642
+ completed(nativeControl);
643
+ assert.equal(nativeResult.exitCode, 1);
644
+ assert.equal(nativeControl.exitCode, 0);
645
+ assert.equal(nativeResult.testReporter, undefined);
646
+ assert.equal(`${nativeResult.stdout}\n${nativeResult.stderr}`.includes(reportPrefix), false);
647
+ const nativeEvents = boundaryControl(nativeControl, f.runId, terminated);
648
+ const nativeDiagnostic = workerDeathIpcDiagnostic(nativeResult);
649
+ disposals.push({ mode: "native", repetition, scenario: terminated.id,
650
+ ...await disposedResourceWorker(nativeEvents, f.root, t.signal) });
651
+ for (const scenario of REPOSITORY_RESOURCE_CASES) {
652
+ const result = results.find(item => item.repetition === repetition && item.recipeId === grade(scenario).id);
653
+ const controlResult = results.find(item => item.repetition === repetition && item.recipeId === f.control(scenario.id).id);
654
+ assert.ok(result);
655
+ assert.ok(controlResult);
656
+ completed(controlResult);
657
+ assert.equal(controlResult.exitCode, 0);
658
+ // Actual preparation, validation and body entry must precede any accepted
659
+ // refusal. Launch/import failures cannot satisfy these resource controls.
660
+ assert.equal(result.stage, "grade");
661
+ assert.equal(result.gradeExecuted, true);
662
+ const preparation = result.preparation?.sharedResults ?? result.trustedPreparationResults;
663
+ assert.equal(preparation.length, 1);
664
+ for (const item of preparation)
665
+ assert.equal(item.exitCode, 0);
666
+ assert.ok(result.candidateValidationResults.length > 0);
667
+ for (const item of result.candidateValidationResults)
668
+ assert.equal(item.exitCode, 0);
669
+ assert.equal(result.testReporter, scenario.runner === "vitest" ? "vitest-v1" : undefined);
670
+ const control = boundaryControl(controlResult, f.runId, scenario);
671
+ const entered = control.events.find(event => event.phase === "body-enter");
672
+ assert.ok(entered);
673
+ const previousCheckout = checkoutByRepetition.get(repetition);
674
+ if (previousCheckout === undefined)
675
+ checkoutByRepetition.set(repetition, entered.process.checkout);
676
+ else
677
+ assert.equal(entered.process.checkout, previousCheckout);
678
+ assert.notEqual(entered.process.checkout, nativeEvents.events[0].process.checkout, "stock and host controls must not share causal event files");
679
+ if (scenario.kind === "deadline") {
680
+ assert.equal(result.timedOut, true, "the repository parent deadline, not Vitest's body timeout, must fire");
681
+ assert.equal(result.outputTruncated, false);
682
+ assert.equal(result.exitCode, null);
683
+ assert.ok(result.durationMs >= RESOURCE_PARENT_DEADLINE_MS);
684
+ assert.equal(`${result.stdout}\n${result.stderr}`.includes(reportPrefix), false, "the killed finite body must not report normal framework completion");
685
+ }
686
+ else if (scenario.kind === "output" && scenario.expected === "unknown") {
687
+ assert.equal(result.timedOut, false);
688
+ assert.equal(result.exitCode, 0, "the bounded real Node test must finish normally despite parent output truncation");
689
+ assert.equal(result.outputTruncated, true, "the actual capture cap must be crossed, not a fabricated flag");
690
+ assert.ok(scenario.chunks * RESOURCE_OUTPUT_CHUNK_BYTES > RESOURCE_OUTPUT_CAP_BYTES);
691
+ assert.equal(Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr), RESOURCE_OUTPUT_CAP_BYTES, "the existing runner must retain exactly its bounded combined ASCII output budget");
692
+ }
693
+ else {
694
+ completed(result);
695
+ assert.equal(result.exitCode, scenario.expected === "pass" ? 0 : 1);
696
+ }
697
+ if (scenario.kind === "termination") {
698
+ // Pinned Tinypool can send teardown after worker IPC closes. Vitest's
699
+ // fatal rejection handler then exits before the parent emits a frame.
700
+ // Require this demonstrated native path, not any arbitrary crash.
701
+ assert.deepEqual(workerDeathIpcDiagnostic(result), nativeDiagnostic);
702
+ assert.equal(repositoryCommandTestEvidenceV1(result, result.recipeId).detail, "the host-selected Vitest reporter did not emit complete execution evidence");
703
+ }
704
+ if (scenario.kind === "output" && scenario.expected === "pass") {
705
+ assert.ok(scenario.chunks * RESOURCE_OUTPUT_CHUNK_BYTES < RESOURCE_OUTPUT_CAP_BYTES);
706
+ assert.ok(Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr) < RESOURCE_OUTPUT_CAP_BYTES);
707
+ }
708
+ assert.equal(repositoryCommandTestEvidenceV1(result, result.recipeId).outcome, scenario.expected);
709
+ disposals.push({ mode: "host", repetition, scenario: scenario.id,
710
+ ...await disposedResourceWorker(control, f.root, t.signal) });
711
+ }
712
+ }
713
+ assert.notEqual(checkoutByRepetition.get(1), checkoutByRepetition.get(2), "repetitions must use separate runner-owned checkouts");
714
+ await f.record("resource-disposal-results.json", disposals);
715
+ f.complete();
716
+ });
717
+ test("real pinned Vitest preserves returned-cleanup timeout and registration-stack semantics", { timeout: 180000 }, async (t) => {
718
+ const f = await fixture(t);
719
+ const replay = (instrumented) => Effect.scoped(Effect.gen(function* () {
720
+ const cache = process.platform === "darwin" ? yield* makeRepositoryPreparationCacheV1({
721
+ repositoryRoot: f.root, commits: [f.commit], trustedPreparationRecipes: f.preparation
722
+ }) : undefined;
723
+ return yield* replayRepositoryCommandsV1({ repositoryRoot: f.root, commit: f.commit,
724
+ trustedPreparationRecipes: f.preparation, candidateValidationRecipes: f.validation,
725
+ ...(cache === undefined ? {} : { preparationCache: cache }),
726
+ recipes: VITEST_PHASE_TIMEOUT_CASES.flatMap(scenario => instrumented
727
+ ? [f.grade(scenario.id), f.control(scenario.id)]
728
+ : [f.stock(scenario.id), f.stockControl(scenario.id)]),
729
+ repetitions: 2 });
730
+ }));
731
+ // Separate runner-owned checkouts prevent phase files from the stock run
732
+ // being mistaken for observations from the instrumented run.
733
+ const baseline = await executeRecorded(f, "timeout-stock", replay(false));
734
+ const phased = await executeRecorded(f, "timeout-phased", replay(true));
735
+ assert.equal(baseline.length, VITEST_PHASE_TIMEOUT_CASES.length * 4);
736
+ assert.equal(phased.length, VITEST_PHASE_TIMEOUT_CASES.length * 4);
737
+ for (const repetition of [1, 2])
738
+ for (const scenario of VITEST_PHASE_TIMEOUT_CASES) {
739
+ const stock = baseline.find(item => item.repetition === repetition && item.recipeId === f.stock(scenario.id).id);
740
+ const stockControl = baseline.find(item => item.repetition === repetition && item.recipeId === f.stockControl(scenario.id).id);
741
+ const result = phased.find(item => item.repetition === repetition && item.recipeId === f.grade(scenario.id).id);
742
+ const controlResult = phased.find(item => item.repetition === repetition && item.recipeId === f.control(scenario.id).id);
743
+ assert.ok(stock);
744
+ assert.ok(stockControl);
745
+ assert.ok(result);
746
+ assert.ok(controlResult);
747
+ for (const item of [stock, stockControl, result, controlResult])
748
+ completed(item);
749
+ assert.equal(stockControl.exitCode, 0);
750
+ assert.equal(controlResult.exitCode, 0);
751
+ assert.equal(stock.testReporter, undefined, "the baseline must not install the host reporter or adapter");
752
+ assert.equal(`${stock.stdout}\n${stock.stderr}`.includes(reportPrefix), false);
753
+ assert.equal(stock.exitCode, scenario.expected === "pass" ? 0 : 1, JSON.stringify(stock));
754
+ const stockReport = oneFrame(stockControl.stdout, stockPrefix);
755
+ assert.equal(stockReport.runId, f.runId);
756
+ assert.equal(stockReport.scenario, scenario.id);
757
+ assert.equal(stockReport.report.success, scenario.expected === "pass");
758
+ assert.equal(stockReport.report.numTotalTests, 1);
759
+ assert.equal(stockReport.report.numPassedTests, scenario.expected === "pass" ? 1 : 0);
760
+ assert.equal(stockReport.report.numFailedTests, scenario.expected === "pass" ? 0 : 1);
761
+ assert.equal(stockReport.report.testResults.length, 1);
762
+ const stockFile = stockReport.report.testResults[0];
763
+ assert.equal(stockFile.status, scenario.expected === "pass" ? "passed" : "failed");
764
+ assert.equal(stockFile.assertionResults.length, 1);
765
+ assert.equal(stockFile.assertionResults[0].status, stockFile.status);
766
+ assert.equal(stockFile.assertionResults[0].failureMessages.length, scenario.expected === "pass" ? 0 : 1);
767
+ const controls = [stockControl, controlResult].map(item => oneFrame(item.stdout, controlPrefix));
768
+ const observations = controls.map(control => {
769
+ assert.equal(control.runId, f.runId);
770
+ assert.equal(control.scenario, scenario.id);
771
+ assert.equal(control.customRunnerImported, false);
772
+ assert.deepEqual(control.events.map(event => event.phase), scenario.events);
773
+ for (const [sequence, event] of control.events.entries()) {
774
+ assert.equal(event.sequence, sequence);
775
+ assert.equal(event.runId, f.runId);
776
+ assert.equal(event.scenario, scenario.id);
777
+ }
778
+ const aborted = control.events.filter(event => event.phase === "hook-cleanup-abort");
779
+ assert.equal(aborted.length, scenario.expected === "pass" ? 0 : 1);
780
+ return {
781
+ phases: control.events.map(event => event.phase),
782
+ aborts: aborted.map(event => {
783
+ assert.ok(event.error);
784
+ assert.equal(event.error.name, "Error");
785
+ assert.ok(event.error.message.startsWith(`Hook timed out in ${scenario.cleanupTimeoutMs}ms.`));
786
+ // Compare the original registration location, not checkout-specific
787
+ // absolute paths or human-readable console diagnostics.
788
+ const registration = event.error.stack.match(/\/test\/register\.mjs:\d+:\d+/u)?.[0];
789
+ assert.ok(registration, "the framework's original cleanup registration stack must survive");
790
+ return { name: event.error.name, message: event.error.message, registration };
791
+ })
792
+ };
793
+ });
794
+ assert.deepEqual(observations[1], observations[0], "instrumentation must preserve the ordinary cleanup lifecycle");
795
+ const report = oneFrame(`${result.stdout}\n${result.stderr}`, reportPrefix);
796
+ assert.equal(report.version, 1);
797
+ assert.equal(report.complete, true, JSON.stringify(report));
798
+ assert.equal(report.starts, 1);
799
+ assert.equal(report.ends, 1);
800
+ assert.equal(report.eventIntegrity, true);
801
+ assert.deepEqual(report.unhandledErrors, scenario.expected === "pass" ? [] : [
802
+ { name: "RouteKitVitestPhaseAttributionError", code: "ROUTEKIT_VITEST_PHASE_UNATTRIBUTED" }
803
+ ]);
804
+ assert.equal(result.exitCode, scenario.expected === "pass" ? 0 : 1, JSON.stringify(result));
805
+ assert.equal(repositoryCommandTestEvidenceV1(result, result.recipeId).outcome, scenario.expected);
806
+ }
807
+ f.complete();
808
+ });
809
+ test("resolved custom runners, invalid CLI options and unrecognized runtime versions are refused before any worker runs", { timeout: 180000 }, async (t) => {
810
+ const f = await fixture(t);
811
+ const cases = ["config-runner", "cli-runner", "unknown-version"];
812
+ for (const kind of cases) {
813
+ const base = f.grade("positive");
814
+ const grade = kind === "cli-runner" ? { ...base, args: [...base.args, "--runner", "./custom-runner.mjs"] } : base;
815
+ const results = await executeRecorded(f, kind, replayRepositoryCommandsV1({ repositoryRoot: f.root, commit: f.commit,
816
+ trustedPreparationRecipes: f.preparation,
817
+ candidateValidationRecipes: kind === "unknown-version" ? [...f.validation, { id: "isolated-version-control",
818
+ kind: "check", cwd: ".", executable: "node", args: ["scripts/unsupported-version.mjs"], timeoutMs: 10000 }] : f.validation,
819
+ fileOverrides: kind === "config-runner" ? [{ path: "vitest.config.mjs", content: 'export default { test: { runner: "./custom-runner.mjs" } };\n' }] : [],
820
+ recipes: [grade, f.control("positive")], repetitions: 1 }));
821
+ assert.equal(results.length, 2, `${kind} must run the extraction control after the refusal`);
822
+ const [result, controlResult] = results;
823
+ assert.ok(result);
824
+ assert.ok(controlResult);
825
+ completed(result);
826
+ completed(controlResult);
827
+ assert.equal(controlResult.exitCode, 0);
828
+ assert.equal(result.exitCode, 1);
829
+ if (kind === "cli-runner") {
830
+ // This pinned Vitest exposes custom runners through configuration, not
831
+ // a CLI flag. Its native argument parser refuses --runner before the
832
+ // host reporter loads; that is not a host phase-contract frame.
833
+ assert.equal(`${result.stdout}\n${result.stderr}`.includes(reportPrefix), false);
834
+ assert.match(result.stderr, /CACError: Unknown option `--runner`/u);
835
+ }
836
+ else {
837
+ const report = oneFrame(`${result.stdout}\n${result.stderr}`, reportPrefix);
838
+ assert.equal(report.complete, false);
839
+ assert.equal(report.error, kind === "unknown-version" ? "unsupported-vitest-phase-runtime" : "unsupported-vitest-custom-runner");
840
+ }
841
+ const control = oneFrame(controlResult.stdout, controlPrefix);
842
+ assert.equal(control.runId, f.runId);
843
+ assert.equal(control.customRunnerImported, false, "custom runner module must never load");
844
+ assert.deepEqual(control.events, [], "normal test worker must not execute before contract refusal");
845
+ assert.equal(repositoryCommandTestEvidenceV1(result, result.recipeId).outcome, "unknown");
846
+ }
847
+ f.complete();
848
+ });