@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,1483 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { constants } from "node:fs";
4
+ import { cp, lstat, mkdir, mkdtemp, readdir, readFile, readlink, realpath, rm, symlink, writeFile } from "node:fs/promises";
5
+ import { tmpdir } from "node:os";
6
+ import { delimiter, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
7
+ import { StringDecoder } from "node:string_decoder";
8
+ import { promisify } from "node:util";
9
+ import { Clock, Context, Effect, Exit, Option, ScopedCache } from "effect";
10
+ import { RepositoryFoundryError } from "../errors.js";
11
+ import { RepositoryFoundryEvidenceReconstruction } from "../services/evidence-reconstruction/service.js";
12
+ import { RepositoryCommandObservability } from "../services/command-observability/service.js";
13
+ import { sanitizeRepositoryCommandOutputV1 } from "./repository-command-diagnostic.js";
14
+ import { repositoryCommandExecutionIncompleteV1 } from "./repository-command-evidence.js";
15
+ import { repositoryNodeTestArgumentsV1 } from "./repository-node-test-reporter.js";
16
+ import { repositoryVitestReporterSourceV1, repositoryVitestTestArgumentsV1 } from "./repository-vitest-reporter.js";
17
+ import { repositoryVitestPhaseAdapterV1 } from "./repository-vitest-phase-adapter.js";
18
+ export const RepositoryCommandExecutionPort = Context.Reference("@velum-labs/routekit-eval-setup/RepositoryCommandExecutionPort", {
19
+ defaultValue: () => undefined
20
+ });
21
+ /** Host-provided, versioned pnpm store populated before an offline invocation. */
22
+ export const RepositoryReadOnlyPnpmStore = Context.Reference("@velum-labs/routekit-eval-setup/RepositoryReadOnlyPnpmStore", { defaultValue: () => undefined });
23
+ const MAX_OUTPUT_BYTES = 2 * 1024 * 1024;
24
+ const MAX_OBSERVED_COMMAND_BYTES = 64 * 1024;
25
+ const MAX_OBSERVED_CHUNK_BYTES = 2 * 1024;
26
+ const MAX_OBSERVED_CHUNKS_PER_FLUSH = 8;
27
+ const OBSERVED_COMMAND_FLUSH_MS = 250;
28
+ const execFilePromise = promisify(execFile);
29
+ const ALLOWED_EXECUTABLES = new Set([
30
+ "corepack",
31
+ "node",
32
+ "pnpm",
33
+ "python",
34
+ "python3",
35
+ "go",
36
+ "cargo"
37
+ ]);
38
+ const commandPath = [dirname(process.execPath), process.env.PATH]
39
+ .filter((value) => value !== undefined && value.length > 0)
40
+ .join(delimiter);
41
+ const corepackHome = process.env.COREPACK_HOME ??
42
+ (process.env.HOME === undefined
43
+ ? undefined
44
+ : join(process.env.HOME, ".cache", "node", "corepack"));
45
+ const trustedPnpmStoreRoot = join(tmpdir(), `routekit-foundry-pnpm-store-${String(process.pid)}-${randomUUID()}`);
46
+ const failure = (operation, detail, cause) => new RepositoryFoundryError({
47
+ operation,
48
+ detail,
49
+ ...(cause === undefined ? {} : { cause })
50
+ });
51
+ const pathWithin = (root, candidate) => {
52
+ const path = relative(root, candidate);
53
+ return path === "" || (path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path));
54
+ };
55
+ const sbplPath = (path) => JSON.stringify(path);
56
+ const createDarwinSandboxProfiles = async (input) => {
57
+ const candidateProfile = join(input.root, "repository-command-candidate.sb");
58
+ const trustedPreparationProfile = join(input.root, "repository-command-trusted-preparation.sb");
59
+ const optionalReadRoots = [
60
+ dirname(dirname(process.execPath)),
61
+ process.env.PNPM_HOME,
62
+ corepackHome,
63
+ process.env.HOME === undefined ? undefined : join(process.env.HOME, "Library", "pnpm"),
64
+ input.trustedPnpmStore
65
+ ].filter((candidate) => candidate !== undefined && candidate.length > 0);
66
+ const readRoots = [
67
+ input.root,
68
+ "/bin",
69
+ "/Library/Developer/CommandLineTools",
70
+ "/sbin",
71
+ "/usr/bin",
72
+ "/usr/local",
73
+ "/opt/homebrew",
74
+ ...optionalReadRoots
75
+ ];
76
+ const common = [
77
+ "(version 1)",
78
+ "(deny default)",
79
+ '(import "system.sb")',
80
+ "(allow process*)",
81
+ // Test runners must be able to reap their own worker processes. This does
82
+ // not permit signals to the host, sibling candidates or unrelated jobs.
83
+ "(allow signal (target children))",
84
+ "(allow file-read-metadata file-test-existence)",
85
+ `(allow file-read* file-test-existence file-map-executable ${readRoots
86
+ .map((candidate) => `(subpath ${sbplPath(candidate)})`)
87
+ .join(" ")})`
88
+ ];
89
+ const writableIsolationRoots = `(allow file-write* (subpath ${sbplPath(input.checkout)}) (subpath ${sbplPath(input.home)}) (subpath ${sbplPath(input.temporary)}))`;
90
+ const candidateSource = [
91
+ ...common,
92
+ writableIsolationRoots,
93
+ `(deny file-write* (subpath ${sbplPath(join(input.checkout, ".git"))}))`,
94
+ "(deny network*)",
95
+ ""
96
+ ].join("\n");
97
+ const trustedPreparationSource = [
98
+ ...common,
99
+ `(allow file-write* (subpath ${sbplPath(input.checkout)}) (subpath ${sbplPath(input.home)}) (subpath ${sbplPath(input.temporary)})${input.trustedPnpmStore === undefined || input.trustedPnpmStoreReadOnly
100
+ ? ""
101
+ : ` (subpath ${sbplPath(input.trustedPnpmStore)})`})`,
102
+ `(deny file-write* (subpath ${sbplPath(join(input.checkout, ".git"))}))`,
103
+ "(allow network*)",
104
+ ""
105
+ ].join("\n");
106
+ await Promise.all([
107
+ writeFile(candidateProfile, candidateSource, {
108
+ encoding: "utf8",
109
+ mode: 0o600
110
+ }),
111
+ writeFile(trustedPreparationProfile, trustedPreparationSource, {
112
+ encoding: "utf8",
113
+ mode: 0o600
114
+ })
115
+ ]);
116
+ return {
117
+ candidate: candidateProfile,
118
+ trustedPreparation: trustedPreparationProfile
119
+ };
120
+ };
121
+ const materializeIsolatedRepository = async (input) => {
122
+ const startedAt = Date.now();
123
+ const root = await realpath(await mkdtemp(join(tmpdir(), input.preparingTemplate ? "routekit-foundry-prepared-" : "routekit-foundry-isolated-")));
124
+ const checkout = join(root, "repository");
125
+ const home = join(root, "home");
126
+ const temporary = join(root, "tmp");
127
+ const archive = join(root, "source.tar");
128
+ await Promise.all([
129
+ mkdir(checkout, { recursive: true }),
130
+ mkdir(home, { recursive: true }),
131
+ mkdir(temporary, { recursive: true })
132
+ ]);
133
+ try {
134
+ if (input.template !== undefined) {
135
+ if (process.platform === "darwin") {
136
+ // Native traversal avoids fs.cp's per-file JS overhead on large pnpm
137
+ // installations. -c uses clonefile with ordinary-copy fallback; -P keeps
138
+ // symlinks intact, and neither mode creates hard links to the template.
139
+ await execFilePromise("/bin/cp", ["-cR", "-P", `${input.template.isolated.checkout}/`, checkout], {
140
+ encoding: "utf8",
141
+ maxBuffer: MAX_OUTPUT_BYTES,
142
+ timeout: 60000,
143
+ env: { PATH: commandPath }
144
+ });
145
+ }
146
+ else {
147
+ await cp(input.template.isolated.checkout, checkout, {
148
+ recursive: true,
149
+ mode: constants.COPYFILE_FICLONE,
150
+ dereference: false,
151
+ verbatimSymlinks: true
152
+ });
153
+ }
154
+ // pnpm shell launchers can embed absolute NODE_PATH prefixes. A fresh clone
155
+ // must resolve its own dependencies, never the immutable template's files.
156
+ for (const path of input.template.relocatedFiles) {
157
+ const destination = join(checkout, path);
158
+ const content = await readFile(destination, "utf8");
159
+ await writeFile(destination, content.replaceAll(input.template.isolated.checkout, checkout));
160
+ }
161
+ }
162
+ else {
163
+ await execFilePromise("git", [
164
+ "-C",
165
+ input.repositoryRoot,
166
+ "archive",
167
+ "--format=tar",
168
+ `--output=${archive}`,
169
+ input.commit
170
+ ], {
171
+ encoding: "utf8",
172
+ maxBuffer: MAX_OUTPUT_BYTES,
173
+ windowsHide: true,
174
+ env: {
175
+ PATH: process.env.PATH,
176
+ HOME: process.env.HOME,
177
+ TMPDIR: process.env.TMPDIR
178
+ }
179
+ });
180
+ await execFilePromise("tar", ["-xf", archive, "-C", checkout], {
181
+ encoding: "utf8",
182
+ maxBuffer: MAX_OUTPUT_BYTES,
183
+ windowsHide: true
184
+ });
185
+ await rm(archive);
186
+ const gitOptions = {
187
+ encoding: "utf8",
188
+ maxBuffer: MAX_OUTPUT_BYTES,
189
+ windowsHide: true
190
+ };
191
+ await execFilePromise("git", ["-C", checkout, "init", "-q"], gitOptions);
192
+ await execFilePromise("git", ["-C", checkout, "config", "user.name", "RouteKit Repository Foundry"], gitOptions);
193
+ await execFilePromise("git", ["-C", checkout, "config", "user.email", "repository-foundry@routekit.invalid"], gitOptions);
194
+ await execFilePromise("git", ["-C", checkout, "config", "commit.gpgsign", "false"], gitOptions);
195
+ await execFilePromise("git", ["-C", checkout, "add", "-A"], gitOptions);
196
+ await execFilePromise("git", [
197
+ "-C",
198
+ checkout,
199
+ "commit",
200
+ "-q",
201
+ "--no-gpg-sign",
202
+ "--date=2000-01-01T00:00:00Z",
203
+ "-m",
204
+ "isolated foundry baseline"
205
+ ], gitOptions);
206
+ }
207
+ const canonicalRoot = await realpath(root);
208
+ const canonicalCheckout = await realpath(checkout);
209
+ const canonicalHome = await realpath(home);
210
+ const canonicalTemporary = await realpath(temporary);
211
+ const sandboxProfiles = process.platform === "darwin"
212
+ ? await createDarwinSandboxProfiles({
213
+ root: canonicalRoot,
214
+ checkout: canonicalCheckout,
215
+ home: canonicalHome,
216
+ temporary: canonicalTemporary,
217
+ ...(input.trustedPnpmStore === undefined
218
+ ? {}
219
+ : {
220
+ trustedPnpmStore: input.trustedPnpmStore,
221
+ trustedPnpmStoreReadOnly: input.trustedPnpmStoreReadOnly ?? false
222
+ })
223
+ })
224
+ : undefined;
225
+ return {
226
+ root: canonicalRoot,
227
+ checkout: canonicalCheckout,
228
+ home: canonicalHome,
229
+ temporary: canonicalTemporary,
230
+ materializationDurationMs: Date.now() - startedAt,
231
+ timings: {
232
+ checkoutDurationMs: Date.now() - startedAt,
233
+ overlayDurationMs: 0,
234
+ cleanupDurationMs: 0
235
+ },
236
+ ...(sandboxProfiles === undefined
237
+ ? {}
238
+ : {
239
+ candidateSandboxProfile: sandboxProfiles.candidate,
240
+ trustedPreparationSandboxProfile: sandboxProfiles.trustedPreparation
241
+ }),
242
+ ...(input.trustedPnpmStore === undefined
243
+ ? {}
244
+ : {
245
+ trustedPnpmStore: input.trustedPnpmStore,
246
+ trustedPnpmStoreReadOnly: input.trustedPnpmStoreReadOnly ?? false
247
+ })
248
+ };
249
+ }
250
+ catch (cause) {
251
+ await rm(root, { recursive: true, force: true });
252
+ throw cause;
253
+ }
254
+ };
255
+ const exactPnpmSpecifier = /^pnpm@(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?$/u;
256
+ const pnpmCommandIndex = (args) => {
257
+ let index = 1;
258
+ while (args[index] === "--filter") {
259
+ const selector = args[index + 1];
260
+ if (selector === undefined || selector.length === 0 || selector.startsWith("-")) {
261
+ return undefined;
262
+ }
263
+ index += 2;
264
+ }
265
+ return index < args.length ? index : undefined;
266
+ };
267
+ const isPnpmInstallRecipe = (recipe) => recipe.executable === "corepack" &&
268
+ exactPnpmSpecifier.test(recipe.args[0] ?? "") &&
269
+ recipe.args[pnpmCommandIndex(recipe.args) ?? -1] === "install";
270
+ const reusableFrozenInstallRecipe = (recipe) => {
271
+ if (recipe.executable !== "corepack" ||
272
+ recipe.cwd !== "." ||
273
+ !exactPnpmSpecifier.test(recipe.args[0] ?? "")) {
274
+ return false;
275
+ }
276
+ const commandIndex = pnpmCommandIndex(recipe.args);
277
+ if (commandIndex === undefined || recipe.args[commandIndex] !== "install")
278
+ return false;
279
+ const required = new Set(["--frozen-lockfile", "--ignore-scripts", "--ignore-pnpmfile"]);
280
+ for (let index = commandIndex + 1; index < recipe.args.length; index += 1) {
281
+ const argument = recipe.args[index];
282
+ if (required.delete(argument) || argument === "--prefer-offline" || argument === "--offline") {
283
+ continue;
284
+ }
285
+ if (argument === "--filter" &&
286
+ (recipe.args[index + 1]?.length ?? 0) > 0 &&
287
+ !recipe.args[index + 1].startsWith("-")) {
288
+ index += 1;
289
+ continue;
290
+ }
291
+ return false;
292
+ }
293
+ return required.size === 0;
294
+ };
295
+ const materializeExactPnpmShim = async (isolatedRoot, recipe) => {
296
+ const specifier = recipe.executable === "corepack" ? recipe.args[0] : undefined;
297
+ if (specifier === undefined || !exactPnpmSpecifier.test(specifier))
298
+ return undefined;
299
+ const directory = join(isolatedRoot, "repository-command-bin");
300
+ await mkdir(directory, { recursive: true });
301
+ if (process.platform === "win32") {
302
+ await writeFile(join(directory, "pnpm.cmd"), `@echo off\r\ncorepack ${specifier} %*\r\n`, {
303
+ encoding: "utf8",
304
+ mode: 0o700
305
+ });
306
+ }
307
+ else {
308
+ await writeFile(join(directory, "pnpm"), `#!/bin/sh\nexec corepack ${specifier} "$@"\n`, {
309
+ encoding: "utf8",
310
+ mode: 0o700
311
+ });
312
+ }
313
+ return directory;
314
+ };
315
+ const validateRecipe = (recipe, checkout, stage) => {
316
+ if (!ALLOWED_EXECUTABLES.has(recipe.executable)) {
317
+ throw new Error(`command executable is not allowed: ${JSON.stringify(recipe.executable)}`);
318
+ }
319
+ if (recipe.cwd.trim().length === 0 ||
320
+ isAbsolute(recipe.cwd) ||
321
+ recipe.cwd.split(/[\\/]/u).includes("..") ||
322
+ recipe.cwd.includes("\0")) {
323
+ throw new Error(`command cwd is not a canonical relative path: ${JSON.stringify(recipe.cwd)}`);
324
+ }
325
+ if (!Number.isInteger(recipe.timeoutMs) ||
326
+ recipe.timeoutMs < 1 ||
327
+ recipe.timeoutMs > 30 * 60_000) {
328
+ throw new Error("command timeout must be between 1 ms and 30 minutes");
329
+ }
330
+ if (recipe.args.some((arg) => arg.includes("\0") || /^(?:--cwd|-C)$/u.test(arg) || /^--cwd=/u.test(arg))) {
331
+ throw new Error("command arguments must not override the reviewed cwd");
332
+ }
333
+ if (stage === "trusted-preparation" &&
334
+ recipe.executable === "corepack" &&
335
+ !exactPnpmSpecifier.test(recipe.args[0] ?? "")) {
336
+ throw new Error("corepack commands require an exact pnpm semantic version");
337
+ }
338
+ if (stage === "trusted-preparation" &&
339
+ isPnpmInstallRecipe(recipe) &&
340
+ !reusableFrozenInstallRecipe(recipe)) {
341
+ throw new Error("trusted pnpm preparation requires a frozen lockfile with scripts and pnpm hooks disabled");
342
+ }
343
+ const cwd = resolve(checkout, recipe.cwd);
344
+ if (!pathWithin(checkout, cwd)) {
345
+ throw new Error("command cwd escapes the isolated checkout");
346
+ }
347
+ return cwd;
348
+ };
349
+ const applyCandidatePatch = (checkout, patch) => {
350
+ if (Buffer.byteLength(patch, "utf8") > MAX_OUTPUT_BYTES) {
351
+ return Effect.fail(failure("prepare-checkout", "candidate patch exceeds the 2 MiB limit"));
352
+ }
353
+ return Effect.callback((resume, interruptionSignal) => {
354
+ const child = spawn("git", ["-C", checkout, "apply", "--whitespace=nowarn", "--recount", "-"], {
355
+ stdio: ["pipe", "ignore", "pipe"],
356
+ env: {
357
+ PATH: process.env.PATH,
358
+ HOME: process.env.HOME,
359
+ TMPDIR: process.env.TMPDIR
360
+ }
361
+ });
362
+ let settled = false;
363
+ let stderr = "";
364
+ child.stderr.setEncoding("utf8");
365
+ child.stderr.on("data", (chunk) => {
366
+ if (Buffer.byteLength(stderr) < MAX_OUTPUT_BYTES)
367
+ stderr += chunk;
368
+ });
369
+ child.once("error", (cause) => {
370
+ if (settled)
371
+ return;
372
+ settled = true;
373
+ resume(Effect.fail(failure("prepare-checkout", "could not apply candidate patch", cause)));
374
+ });
375
+ child.once("close", (code) => {
376
+ if (settled)
377
+ return;
378
+ settled = true;
379
+ resume(code === 0
380
+ ? Effect.void
381
+ : Effect.fail(failure("prepare-checkout", `candidate patch did not apply: ${stderr.trim()}`)));
382
+ });
383
+ const interrupt = () => {
384
+ child.kill("SIGKILL");
385
+ };
386
+ interruptionSignal.addEventListener("abort", interrupt, { once: true });
387
+ child.stdin.end(patch, "utf8");
388
+ return Effect.sync(() => {
389
+ interruptionSignal.removeEventListener("abort", interrupt);
390
+ if (!settled)
391
+ child.kill("SIGKILL");
392
+ });
393
+ });
394
+ };
395
+ // Host writes are outside the candidate sandbox. A lexical path check alone
396
+ // cannot protect the shared dependency store or host files from linked paths.
397
+ // Each clone is private and no candidate runs while these overlays are installed.
398
+ const assertHostOverlayDestination = async (checkout, path) => {
399
+ if (path.length === 0 ||
400
+ isAbsolute(path) ||
401
+ path.includes("\\") ||
402
+ path.includes("\0") ||
403
+ path.split("/").some((part) => part === "" || part === "." || part === "..")) {
404
+ throw new Error("host overlay destination is not a canonical repository-relative file path");
405
+ }
406
+ const root = await lstat(checkout);
407
+ if (root.isSymbolicLink() || !root.isDirectory()) {
408
+ throw new Error("host overlay checkout root is not a regular directory");
409
+ }
410
+ let current = checkout;
411
+ const parts = path.split("/");
412
+ for (const [index, part] of parts.entries()) {
413
+ current = join(current, part);
414
+ let metadata;
415
+ try {
416
+ metadata = await lstat(current);
417
+ }
418
+ catch (cause) {
419
+ if (cause !== null &&
420
+ typeof cause === "object" &&
421
+ "code" in cause &&
422
+ cause.code === "ENOENT") {
423
+ return;
424
+ }
425
+ throw cause;
426
+ }
427
+ if (metadata.isSymbolicLink()) {
428
+ throw new Error("host overlay destination traverses a symbolic link");
429
+ }
430
+ if (index < parts.length - 1 ? !metadata.isDirectory() : !metadata.isFile()) {
431
+ throw new Error("host overlay destination is not a regular file beneath regular directories");
432
+ }
433
+ }
434
+ };
435
+ const applyOverlays = Effect.fn("RepositoryFoundry.applyCommandOverlays")(function* (input) {
436
+ for (const overlay of input.overlays) {
437
+ if (overlay.path.trim().length === 0 ||
438
+ isAbsolute(overlay.path) ||
439
+ overlay.path.split(/[\\/]/u).includes("..") ||
440
+ overlay.path.includes("\0")) {
441
+ return yield* failure("prepare-checkout", `overlay path is unsafe: ${JSON.stringify(overlay.path)}`);
442
+ }
443
+ const destination = resolve(input.checkout, overlay.path);
444
+ if (!pathWithin(input.checkout, destination)) {
445
+ return yield* failure("prepare-checkout", `overlay path escapes checkout: ${JSON.stringify(overlay.path)}`);
446
+ }
447
+ const content = yield* Effect.tryPromise({
448
+ try: async () => {
449
+ const result = await execFilePromise("git", ["-C", input.repositoryRoot, "show", `${overlay.sourceCommit}:${overlay.path}`], {
450
+ encoding: "buffer",
451
+ maxBuffer: MAX_OUTPUT_BYTES,
452
+ windowsHide: true
453
+ });
454
+ return result.stdout;
455
+ },
456
+ catch: (cause) => failure("prepare-checkout", `could not read reviewed overlay ${overlay.sourceCommit}:${overlay.path}`, cause)
457
+ });
458
+ yield* Effect.tryPromise({
459
+ try: async () => {
460
+ await assertHostOverlayDestination(input.checkout, overlay.path);
461
+ await mkdir(dirname(destination), { recursive: true });
462
+ await writeFile(destination, content);
463
+ },
464
+ catch: (cause) => failure("prepare-checkout", `could not materialize reviewed overlay ${JSON.stringify(overlay.path)}`, cause)
465
+ });
466
+ }
467
+ });
468
+ const applyFileOverrides = Effect.fn("RepositoryFoundry.applyCommandFileOverrides")(function* (input) {
469
+ for (const override of input.overrides) {
470
+ if (override.path.trim().length === 0 ||
471
+ isAbsolute(override.path) ||
472
+ override.path.split(/[\\/]/u).includes("..") ||
473
+ override.path.includes("\0")) {
474
+ return yield* failure("prepare-checkout", `file override path is unsafe: ${JSON.stringify(override.path)}`);
475
+ }
476
+ const destination = resolve(input.checkout, override.path);
477
+ if (!pathWithin(input.checkout, destination)) {
478
+ return yield* failure("prepare-checkout", `file override path escapes checkout: ${JSON.stringify(override.path)}`);
479
+ }
480
+ yield* Effect.tryPromise({
481
+ try: async () => {
482
+ await assertHostOverlayDestination(input.checkout, override.path);
483
+ if ("delete" in override) {
484
+ try {
485
+ const metadata = await lstat(destination);
486
+ if (metadata.isSymbolicLink() || !metadata.isFile()) {
487
+ throw new Error("file deletion override can only remove a regular file");
488
+ }
489
+ await rm(destination);
490
+ }
491
+ catch (cause) {
492
+ if (cause !== null &&
493
+ typeof cause === "object" &&
494
+ "code" in cause &&
495
+ cause.code === "ENOENT") {
496
+ return;
497
+ }
498
+ throw cause;
499
+ }
500
+ return;
501
+ }
502
+ await mkdir(dirname(destination), { recursive: true });
503
+ await writeFile(destination, override.content, "utf8");
504
+ },
505
+ catch: (cause) => failure("prepare-checkout", `could not materialize file override ${JSON.stringify(override.path)}`, cause)
506
+ });
507
+ }
508
+ });
509
+ const applyContentOverlays = Effect.fn("RepositoryFoundry.applyCommandContentOverlays")(function* (input) {
510
+ for (const overlay of input.overlays) {
511
+ if (overlay.path.trim().length === 0 ||
512
+ isAbsolute(overlay.path) ||
513
+ overlay.path.split(/[\\/]/u).includes("..") ||
514
+ overlay.path.includes("\0")) {
515
+ return yield* failure("prepare-checkout", `content overlay path is unsafe: ${JSON.stringify(overlay.path)}`);
516
+ }
517
+ const destination = resolve(input.checkout, overlay.path);
518
+ if (!pathWithin(input.checkout, destination)) {
519
+ return yield* failure("prepare-checkout", `content overlay path escapes checkout: ${JSON.stringify(overlay.path)}`);
520
+ }
521
+ yield* Effect.tryPromise({
522
+ try: async () => {
523
+ await assertHostOverlayDestination(input.checkout, overlay.path);
524
+ await mkdir(dirname(destination), { recursive: true });
525
+ await writeFile(destination, overlay.content, "utf8");
526
+ },
527
+ catch: (cause) => failure("prepare-checkout", `could not materialize content overlay ${JSON.stringify(overlay.path)}`, cause)
528
+ });
529
+ }
530
+ });
531
+ const runCommand = Effect.fn("RepositoryFoundry.runCommand")(function* (input) {
532
+ const cwd = yield* Effect.try({
533
+ try: () => validateRecipe(input.recipe, input.isolated.checkout, input.stage),
534
+ catch: (cause) => failure("run-command", cause instanceof Error ? cause.message : String(cause), cause)
535
+ });
536
+ const vitestReporterPath = join(input.isolated.root, `vitest-reporter-${createHash("sha256").update(input.recipe.id).digest("hex")}.mjs`);
537
+ const vitestArguments = input.stage === "grade"
538
+ ? repositoryVitestTestArgumentsV1(input.recipe, vitestReporterPath)
539
+ : undefined;
540
+ if (vitestArguments !== undefined) {
541
+ yield* Effect.tryPromise({
542
+ try: async () => {
543
+ const phaseAdapterPath = `${vitestReporterPath}.phase.mjs`;
544
+ const phase = await repositoryVitestPhaseAdapterV1({
545
+ cwd,
546
+ checkout: input.isolated.checkout
547
+ });
548
+ if (phase.supported) {
549
+ await writeFile(phaseAdapterPath, phase.source, {
550
+ encoding: "utf8",
551
+ mode: 0o600,
552
+ flag: "wx"
553
+ });
554
+ }
555
+ await writeFile(vitestReporterPath, repositoryVitestReporterSourceV1({
556
+ phaseAdapterPath,
557
+ runtimeSupported: phase.supported
558
+ }), {
559
+ encoding: "utf8",
560
+ mode: 0o600,
561
+ flag: "wx"
562
+ });
563
+ },
564
+ catch: (cause) => failure("run-command", "could not materialize the host-owned Vitest phase adapter", cause)
565
+ });
566
+ }
567
+ const reporterMetadata = vitestArguments === undefined ? {} : { testReporter: "vitest-v1" };
568
+ const commandArgs = vitestArguments ??
569
+ (input.stage === "grade" ? repositoryNodeTestArgumentsV1(input.recipe) : input.recipe.args);
570
+ const exactPnpmBin = yield* Effect.tryPromise({
571
+ try: () => materializeExactPnpmShim(input.isolated.root, input.recipe),
572
+ catch: (cause) => failure("run-command", "could not materialize the exact pnpm command shim", cause)
573
+ });
574
+ const commandObservability = yield* Effect.serviceOption(RepositoryCommandObservability);
575
+ const executionPort = yield* RepositoryCommandExecutionPort;
576
+ if (executionPort !== undefined) {
577
+ const startedAt = yield* Clock.currentTimeMillis;
578
+ const commandId = randomUUID();
579
+ const observe = (event) => {
580
+ if (Option.isSome(commandObservability)) {
581
+ try {
582
+ commandObservability.value.observe(event);
583
+ }
584
+ catch {
585
+ /* telemetry never changes scientific decisions */
586
+ }
587
+ }
588
+ };
589
+ const common = {
590
+ version: 1,
591
+ commandId,
592
+ recipeId: input.recipe.id,
593
+ repetition: input.repetition,
594
+ stage: input.stage
595
+ };
596
+ observe({ ...common, kind: "started", at: new Date(startedAt).toISOString() });
597
+ const sandboxProfile = input.stage === "trusted-preparation"
598
+ ? input.isolated.trustedPreparationSandboxProfile
599
+ : input.isolated.candidateSandboxProfile;
600
+ const result = yield* executionPort.run({
601
+ operationId: commandId,
602
+ cwd,
603
+ executable: sandboxProfile === undefined ? input.recipe.executable : "/usr/bin/sandbox-exec",
604
+ args: sandboxProfile === undefined
605
+ ? commandArgs
606
+ : ["-f", sandboxProfile, input.recipe.executable, ...commandArgs],
607
+ environment: {
608
+ PATH: exactPnpmBin === undefined ? commandPath : [exactPnpmBin, commandPath].join(delimiter),
609
+ HOME: input.isolated.home,
610
+ TMPDIR: input.isolated.temporary,
611
+ ...(corepackHome === undefined ? {} : { COREPACK_HOME: corepackHome }),
612
+ ...(input.isolated.trustedPnpmStore === undefined
613
+ ? {}
614
+ : {
615
+ npm_config_store_dir: input.isolated.trustedPnpmStore,
616
+ pnpm_config_store_dir: input.isolated.trustedPnpmStore,
617
+ ...(input.isolated.trustedPnpmStoreReadOnly
618
+ ? { pnpm_config_frozen_store: "true", npm_config_frozen_store: "true" }
619
+ : {}),
620
+ ...(input.stage === "trusted-preparation"
621
+ ? {}
622
+ : {
623
+ npm_config_offline: "true",
624
+ pnpm_config_offline: "true",
625
+ pnpm_config_verify_deps_before_run: "false"
626
+ })
627
+ }),
628
+ CI: "1",
629
+ NO_COLOR: "1",
630
+ FORCE_COLOR: "0",
631
+ PORTLESS: "0"
632
+ },
633
+ timeoutMs: input.recipe.timeoutMs
634
+ });
635
+ const endedAt = yield* Clock.currentTimeMillis;
636
+ let sequence = 0;
637
+ let commandBytes = 0;
638
+ for (const stream of ["stdout", "stderr"]) {
639
+ const bytes = Buffer.from(sanitizeRepositoryCommandOutputV1(result[stream]), "utf8");
640
+ const decoder = new StringDecoder("utf8");
641
+ for (let offset = 0; offset < bytes.length; offset += MAX_OBSERVED_CHUNK_BYTES) {
642
+ const chunk = decoder.write(bytes.subarray(offset, offset + MAX_OBSERVED_CHUNK_BYTES)) +
643
+ (offset + MAX_OBSERVED_CHUNK_BYTES >= bytes.length ? decoder.end() : "");
644
+ commandBytes += Buffer.byteLength(chunk);
645
+ sequence += 1;
646
+ observe({
647
+ ...common,
648
+ kind: "output",
649
+ at: new Date(endedAt).toISOString(),
650
+ stream,
651
+ sequence,
652
+ chunk,
653
+ chunkBytes: Buffer.byteLength(chunk),
654
+ commandBytes,
655
+ truncated: false
656
+ });
657
+ }
658
+ }
659
+ observe({
660
+ ...common,
661
+ kind: "completed",
662
+ at: new Date(endedAt).toISOString(),
663
+ exitCode: result.exitCode,
664
+ timedOut: result.timedOut ?? false,
665
+ durationMs: endedAt - startedAt,
666
+ commandBytes,
667
+ truncated: false
668
+ });
669
+ return {
670
+ recipeId: input.recipe.id,
671
+ commit: input.commit,
672
+ repetition: input.repetition,
673
+ stage: input.stage,
674
+ exitCode: result.exitCode,
675
+ timedOut: result.timedOut ?? false,
676
+ outputTruncated: false,
677
+ stdout: sanitizeRepositoryCommandOutputV1(result.stdout),
678
+ stderr: sanitizeRepositoryCommandOutputV1(result.stderr),
679
+ durationMs: endedAt - startedAt,
680
+ ...reporterMetadata
681
+ };
682
+ }
683
+ return yield* Effect.callback((resume, interruptionSignal) => {
684
+ const startedAt = Date.now();
685
+ const commandId = randomUUID();
686
+ const observe = (event) => {
687
+ if (Option.isNone(commandObservability))
688
+ return;
689
+ try {
690
+ commandObservability.value.observe(event);
691
+ }
692
+ catch {
693
+ // Observability is best-effort and must never affect repository execution.
694
+ }
695
+ };
696
+ const sandboxProfile = input.stage === "trusted-preparation"
697
+ ? input.isolated.trustedPreparationSandboxProfile
698
+ : input.isolated.candidateSandboxProfile;
699
+ const executable = sandboxProfile === undefined ? input.recipe.executable : "/usr/bin/sandbox-exec";
700
+ const args = sandboxProfile === undefined
701
+ ? [...commandArgs]
702
+ : ["-f", sandboxProfile, input.recipe.executable, ...commandArgs];
703
+ const detached = process.platform !== "win32";
704
+ const child = spawn(executable, args, {
705
+ cwd,
706
+ stdio: ["ignore", "pipe", "pipe"],
707
+ detached,
708
+ env: {
709
+ PATH: exactPnpmBin === undefined ? commandPath : [exactPnpmBin, commandPath].join(delimiter),
710
+ HOME: input.isolated.home,
711
+ TMPDIR: input.isolated.temporary,
712
+ ...(corepackHome === undefined ? {} : { COREPACK_HOME: corepackHome }),
713
+ ...(input.isolated.trustedPnpmStore === undefined
714
+ ? {}
715
+ : {
716
+ // pnpm 11 reads pnpm_config_ settings. Keep the npm_config_
717
+ // spelling as well for repositories pinned to older pnpm.
718
+ npm_config_store_dir: input.isolated.trustedPnpmStore,
719
+ pnpm_config_store_dir: input.isolated.trustedPnpmStore,
720
+ ...(input.isolated.trustedPnpmStoreReadOnly
721
+ ? { pnpm_config_frozen_store: "true", npm_config_frozen_store: "true" }
722
+ : {}),
723
+ ...(input.stage === "trusted-preparation"
724
+ ? {}
725
+ : {
726
+ npm_config_offline: "true",
727
+ pnpm_config_offline: "true",
728
+ pnpm_config_verify_deps_before_run: "false"
729
+ })
730
+ }),
731
+ CI: "1",
732
+ NO_COLOR: "1",
733
+ FORCE_COLOR: "0",
734
+ PORTLESS: "0"
735
+ }
736
+ });
737
+ observe({
738
+ version: 1,
739
+ kind: "started",
740
+ at: new Date(startedAt).toISOString(),
741
+ commandId,
742
+ recipeId: input.recipe.id,
743
+ repetition: input.repetition,
744
+ stage: input.stage
745
+ });
746
+ let stdout = "";
747
+ let stderr = "";
748
+ let outputBytes = 0;
749
+ let outputTruncated = false;
750
+ let observedCommandBytes = 0;
751
+ let observedSequence = 0;
752
+ let observedTruncated = false;
753
+ let observedContentDropped = false;
754
+ let observedTimer;
755
+ const observedPending = [];
756
+ const streamState = {
757
+ stdout: { decoder: new StringDecoder("utf8"), line: "", privateKey: false },
758
+ stderr: { decoder: new StringDecoder("utf8"), line: "", privateKey: false }
759
+ };
760
+ let timedOut = false;
761
+ let settled = false;
762
+ let forcedSettlement;
763
+ const takeUtf8Prefix = (value, maximumBytes) => {
764
+ if (Buffer.byteLength(value, "utf8") <= maximumBytes)
765
+ return value;
766
+ let bytes = 0;
767
+ let prefix = "";
768
+ for (const character of value) {
769
+ const width = Buffer.byteLength(character, "utf8");
770
+ if (bytes + width > maximumBytes)
771
+ break;
772
+ prefix += character;
773
+ bytes += width;
774
+ }
775
+ return prefix;
776
+ };
777
+ const pendingBytes = () => observedPending.reduce((total, entry) => total + Buffer.byteLength(entry.text, "utf8"), 0);
778
+ const flushObserved = (maximumChunks = MAX_OBSERVED_CHUNKS_PER_FLUSH, scheduleRemainder = true) => {
779
+ observedTimer = undefined;
780
+ let flushed = 0;
781
+ while (flushed < maximumChunks) {
782
+ const current = observedPending[0];
783
+ if (current === undefined)
784
+ break;
785
+ const chunk = takeUtf8Prefix(current.text, MAX_OBSERVED_CHUNK_BYTES);
786
+ if (chunk.length === 0) {
787
+ observedPending.shift();
788
+ continue;
789
+ }
790
+ const chunkBytes = Buffer.byteLength(chunk, "utf8");
791
+ current.text = current.text.slice(chunk.length);
792
+ if (current.text.length === 0)
793
+ observedPending.shift();
794
+ observedCommandBytes += chunkBytes;
795
+ observe({
796
+ version: 1,
797
+ kind: "output",
798
+ at: new Date().toISOString(),
799
+ commandId,
800
+ recipeId: input.recipe.id,
801
+ repetition: input.repetition,
802
+ stage: input.stage,
803
+ stream: current.stream,
804
+ sequence: ++observedSequence,
805
+ chunk,
806
+ chunkBytes,
807
+ commandBytes: observedCommandBytes,
808
+ truncated: observedTruncated || observedContentDropped
809
+ });
810
+ flushed += 1;
811
+ }
812
+ if (scheduleRemainder && observedPending.length > 0) {
813
+ observedTimer = setTimeout(flushObserved, OBSERVED_COMMAND_FLUSH_MS);
814
+ }
815
+ };
816
+ const scheduleObservedFlush = () => {
817
+ if (observedTimer !== undefined || observedPending.length === 0)
818
+ return;
819
+ observedTimer = setTimeout(flushObserved, OBSERVED_COMMAND_FLUSH_MS);
820
+ };
821
+ const enqueueObserved = (stream, value) => {
822
+ if (value.length === 0 || observedTruncated)
823
+ return;
824
+ const remaining = MAX_OBSERVED_COMMAND_BYTES - observedCommandBytes - pendingBytes();
825
+ if (remaining <= 0) {
826
+ observedTruncated = true;
827
+ return;
828
+ }
829
+ const sanitized = sanitizeRepositoryCommandOutputV1(value);
830
+ const accepted = takeUtf8Prefix(sanitized, remaining);
831
+ if (accepted.length === 0)
832
+ return;
833
+ if (Buffer.byteLength(accepted, "utf8") < Buffer.byteLength(sanitized, "utf8")) {
834
+ observedTruncated = true;
835
+ }
836
+ const last = observedPending.at(-1);
837
+ if (last?.stream === stream)
838
+ last.text += accepted;
839
+ else
840
+ observedPending.push({ stream, text: accepted });
841
+ scheduleObservedFlush();
842
+ };
843
+ const acceptObservedText = (stream, value, final = false) => {
844
+ if (observedTruncated)
845
+ return;
846
+ const state = streamState[stream];
847
+ state.line += value;
848
+ const pieces = state.line.split(/(?<=\n)/u);
849
+ state.line = final ? "" : (pieces.pop() ?? "");
850
+ if (Buffer.byteLength(state.line, "utf8") > MAX_OBSERVED_COMMAND_BYTES) {
851
+ // Never retain an unbounded unterminated line. Dropping the line is
852
+ // safer than cutting through a credential-shaped value.
853
+ state.line = "";
854
+ observedContentDropped = true;
855
+ }
856
+ let safe = "";
857
+ for (const line of pieces) {
858
+ if (state.privateKey) {
859
+ if (/-----END [A-Z0-9 ]*PRIVATE KEY-----/u.test(line)) {
860
+ safe += "[redacted]\n";
861
+ state.privateKey = false;
862
+ }
863
+ continue;
864
+ }
865
+ if (/-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/u.test(line)) {
866
+ if (/-----END [A-Z0-9 ]*PRIVATE KEY-----/u.test(line)) {
867
+ safe += sanitizeRepositoryCommandOutputV1(line);
868
+ }
869
+ else {
870
+ state.privateKey = true;
871
+ }
872
+ continue;
873
+ }
874
+ if (Buffer.byteLength(line, "utf8") > MAX_OBSERVED_COMMAND_BYTES) {
875
+ observedContentDropped = true;
876
+ continue;
877
+ }
878
+ safe += line;
879
+ }
880
+ if (final && state.privateKey) {
881
+ safe += "[redacted]\n";
882
+ state.privateKey = false;
883
+ }
884
+ enqueueObserved(stream, safe);
885
+ };
886
+ const finalizeObserved = () => {
887
+ if (observedTimer !== undefined) {
888
+ clearTimeout(observedTimer);
889
+ observedTimer = undefined;
890
+ }
891
+ for (const stream of ["stdout", "stderr"]) {
892
+ const state = streamState[stream];
893
+ acceptObservedText(stream, state.decoder.end(), true);
894
+ }
895
+ if (observedTimer !== undefined) {
896
+ clearTimeout(observedTimer);
897
+ observedTimer = undefined;
898
+ }
899
+ flushObserved(Number.POSITIVE_INFINITY, false);
900
+ };
901
+ const observeCompleted = (exitCode, commandTimedOut) => {
902
+ finalizeObserved();
903
+ observe({
904
+ version: 1,
905
+ kind: "completed",
906
+ at: new Date().toISOString(),
907
+ commandId,
908
+ recipeId: input.recipe.id,
909
+ repetition: input.repetition,
910
+ stage: input.stage,
911
+ exitCode,
912
+ timedOut: commandTimedOut,
913
+ durationMs: Date.now() - startedAt,
914
+ commandBytes: observedCommandBytes,
915
+ truncated: observedTruncated || observedContentDropped
916
+ });
917
+ };
918
+ const killProcessTree = () => {
919
+ if (detached && child.pid !== undefined) {
920
+ try {
921
+ process.kill(-child.pid, "SIGKILL");
922
+ return;
923
+ }
924
+ catch {
925
+ // Fall through if the process group already exited.
926
+ }
927
+ }
928
+ child.kill("SIGKILL");
929
+ };
930
+ const settleTimedOut = () => {
931
+ if (settled)
932
+ return;
933
+ settled = true;
934
+ child.stdout.destroy();
935
+ child.stderr.destroy();
936
+ observeCompleted(null, true);
937
+ resume(Effect.succeed({
938
+ recipeId: input.recipe.id,
939
+ commit: input.commit,
940
+ repetition: input.repetition,
941
+ stage: input.stage,
942
+ exitCode: null,
943
+ timedOut: true,
944
+ outputTruncated,
945
+ stdout,
946
+ stderr,
947
+ durationMs: Date.now() - startedAt,
948
+ ...reporterMetadata
949
+ }));
950
+ };
951
+ const append = (target, chunk) => {
952
+ acceptObservedText(target, streamState[target].decoder.write(chunk));
953
+ const remaining = MAX_OUTPUT_BYTES - outputBytes;
954
+ if (remaining <= 0) {
955
+ outputTruncated = true;
956
+ return;
957
+ }
958
+ const accepted = chunk.byteLength <= remaining ? chunk : chunk.subarray(0, remaining);
959
+ outputBytes += accepted.byteLength;
960
+ if (accepted.byteLength < chunk.byteLength)
961
+ outputTruncated = true;
962
+ if (target === "stdout")
963
+ stdout += accepted.toString("utf8");
964
+ else
965
+ stderr += accepted.toString("utf8");
966
+ };
967
+ child.stdout.on("data", (chunk) => append("stdout", chunk));
968
+ child.stderr.on("data", (chunk) => append("stderr", chunk));
969
+ const timeout = setTimeout(() => {
970
+ timedOut = true;
971
+ killProcessTree();
972
+ forcedSettlement = setTimeout(settleTimedOut, 2_000);
973
+ }, input.recipe.timeoutMs);
974
+ child.once("error", (cause) => {
975
+ if (settled)
976
+ return;
977
+ settled = true;
978
+ clearTimeout(timeout);
979
+ if (forcedSettlement !== undefined)
980
+ clearTimeout(forcedSettlement);
981
+ observeCompleted(null, false);
982
+ resume(Effect.fail(failure("run-command", `could not start ${input.recipe.executable}`, cause)));
983
+ });
984
+ child.once("close", (exitCode) => {
985
+ if (settled)
986
+ return;
987
+ settled = true;
988
+ clearTimeout(timeout);
989
+ if (forcedSettlement !== undefined)
990
+ clearTimeout(forcedSettlement);
991
+ observeCompleted(exitCode, timedOut);
992
+ resume(Effect.succeed({
993
+ recipeId: input.recipe.id,
994
+ commit: input.commit,
995
+ repetition: input.repetition,
996
+ stage: input.stage,
997
+ exitCode,
998
+ timedOut,
999
+ outputTruncated,
1000
+ stdout,
1001
+ stderr,
1002
+ durationMs: Date.now() - startedAt,
1003
+ ...reporterMetadata
1004
+ }));
1005
+ });
1006
+ const interrupt = () => {
1007
+ killProcessTree();
1008
+ };
1009
+ interruptionSignal.addEventListener("abort", interrupt, { once: true });
1010
+ return Effect.sync(() => {
1011
+ interruptionSignal.removeEventListener("abort", interrupt);
1012
+ clearTimeout(timeout);
1013
+ if (forcedSettlement !== undefined)
1014
+ clearTimeout(forcedSettlement);
1015
+ if (observedTimer !== undefined)
1016
+ clearTimeout(observedTimer);
1017
+ if (!settled)
1018
+ killProcessTree();
1019
+ });
1020
+ });
1021
+ });
1022
+ const commandPassed = (result) => result.exitCode === 0 && !repositoryCommandExecutionIncompleteV1(result);
1023
+ const runRecipeSequence = Effect.fn("RepositoryFoundry.runCommandRecipeSequence")(function* (input) {
1024
+ const results = [];
1025
+ for (const recipe of input.recipes) {
1026
+ const result = yield* runCommand({
1027
+ isolated: input.isolated,
1028
+ commit: input.commit,
1029
+ repetition: input.repetition,
1030
+ stage: input.stage,
1031
+ recipe
1032
+ });
1033
+ results.push(result);
1034
+ if (!commandPassed(result))
1035
+ break;
1036
+ }
1037
+ return results;
1038
+ });
1039
+ const terminalResult = (terminal, input) => ({
1040
+ ...terminal,
1041
+ gradeExecuted: input.gradeExecuted,
1042
+ trustedPreparationResults: input.trustedPreparationResults,
1043
+ candidateValidationResults: input.candidateValidationResults,
1044
+ ...(input.preparation === undefined ? {} : { preparation: input.preparation }),
1045
+ ...(input.isolationTimings === undefined ? {} : { isolationTimings: input.isolationTimings })
1046
+ });
1047
+ const acquireCheckout = (root, commit, trustedPnpmStore, template, preparingTemplate) => Effect.gen(function* () {
1048
+ const readOnlyStore = yield* RepositoryReadOnlyPnpmStore;
1049
+ return yield* Effect.acquireRelease(Effect.tryPromise({
1050
+ try: () => materializeIsolatedRepository({
1051
+ repositoryRoot: root,
1052
+ commit,
1053
+ ...(trustedPnpmStore === undefined
1054
+ ? {}
1055
+ : { trustedPnpmStore, trustedPnpmStoreReadOnly: readOnlyStore !== undefined }),
1056
+ ...(template === undefined ? {} : { template }),
1057
+ ...(preparingTemplate === undefined ? {} : { preparingTemplate })
1058
+ }),
1059
+ catch: (cause) => failure("prepare-checkout", "could not materialize isolated single-commit repository", cause)
1060
+ }), (isolated) => Effect.tryPromise({
1061
+ try: async () => {
1062
+ const startedAt = Date.now();
1063
+ try {
1064
+ await rm(isolated.root, { recursive: true, force: true });
1065
+ }
1066
+ finally {
1067
+ isolated.timings.cleanupDurationMs = Date.now() - startedAt;
1068
+ }
1069
+ },
1070
+ catch: () => undefined
1071
+ }).pipe(Effect.ignore));
1072
+ });
1073
+ const resolveTrustedPnpmStore = Effect.fn("RepositoryFoundry.resolveTrustedPnpmStore")(function* (recipes) {
1074
+ const specifiers = [
1075
+ ...new Set(recipes.flatMap((recipe) => isPnpmInstallRecipe(recipe)
1076
+ ? [recipe.args[0]]
1077
+ : []))
1078
+ ];
1079
+ if (specifiers.length === 0)
1080
+ return undefined;
1081
+ if (specifiers.length !== 1) {
1082
+ return yield* failure("prepare-checkout", "trusted preparation must use one exact pnpm version");
1083
+ }
1084
+ const readOnlyStore = yield* RepositoryReadOnlyPnpmStore;
1085
+ if (readOnlyStore !== undefined) {
1086
+ if (!isAbsolute(readOnlyStore) ||
1087
+ !recipes.every((recipe) => isPnpmInstallRecipe(recipe) &&
1088
+ recipe.args.includes("--offline"))) {
1089
+ return yield* failure("prepare-checkout", "a host-provided read-only pnpm store requires an absolute path and offline installation");
1090
+ }
1091
+ return yield* Effect.tryPromise({
1092
+ try: async () => {
1093
+ const path = await realpath(readOnlyStore);
1094
+ if (!(await lstat(path)).isDirectory()) {
1095
+ throw new Error("the prehydrated pnpm store is not a directory");
1096
+ }
1097
+ return path;
1098
+ },
1099
+ catch: (cause) => failure("prepare-checkout", "the host-provided read-only pnpm store is unavailable", cause)
1100
+ });
1101
+ }
1102
+ const path = yield* Effect.tryPromise({
1103
+ try: async () => {
1104
+ await mkdir(trustedPnpmStoreRoot, { recursive: true, mode: 0o700 });
1105
+ const result = await execFilePromise("corepack", [specifiers[0], "store", "path", "--silent"], {
1106
+ encoding: "utf8",
1107
+ maxBuffer: MAX_OUTPUT_BYTES,
1108
+ windowsHide: true,
1109
+ env: {
1110
+ PATH: commandPath,
1111
+ HOME: process.env.HOME,
1112
+ TMPDIR: process.env.TMPDIR,
1113
+ ...(corepackHome === undefined ? {} : { COREPACK_HOME: corepackHome }),
1114
+ // pnpm derives its default store from the operating-system account
1115
+ // home, not HOME. Immutable sandbox images deliberately make that
1116
+ // account home read-only, so bind the trusted content-addressed
1117
+ // store to a private writable runtime directory explicitly.
1118
+ npm_config_store_dir: trustedPnpmStoreRoot,
1119
+ pnpm_config_store_dir: trustedPnpmStoreRoot
1120
+ }
1121
+ });
1122
+ const requested = result.stdout.trim();
1123
+ if (requested.length === 0 ||
1124
+ !isAbsolute(requested) ||
1125
+ !pathWithin(trustedPnpmStoreRoot, requested)) {
1126
+ throw new Error("pnpm returned a store path outside the trusted runtime root");
1127
+ }
1128
+ await mkdir(requested, { recursive: true });
1129
+ return realpath(requested);
1130
+ },
1131
+ catch: (cause) => failure("prepare-checkout", "could not resolve the trusted pnpm content-addressed store", cause)
1132
+ });
1133
+ return path;
1134
+ });
1135
+ const reusableFrozenInstall = (recipes) => {
1136
+ return recipes.length === 1 && reusableFrozenInstallRecipe(recipes[0]);
1137
+ };
1138
+ /** Normalize generated links once, and inventory pnpm files needing clone-local prefixes. */
1139
+ const prepareTemplateRelocation = async (isolated) => {
1140
+ const relocatedFiles = [];
1141
+ const visit = async (directory) => {
1142
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
1143
+ const path = join(directory, entry.name);
1144
+ if (entry.isSymbolicLink()) {
1145
+ const target = await readlink(path);
1146
+ const resolved = resolve(directory, target);
1147
+ if (pathWithin(isolated.checkout, resolved)) {
1148
+ if (isAbsolute(target)) {
1149
+ await rm(path);
1150
+ await symlink(relative(directory, resolved), path);
1151
+ }
1152
+ }
1153
+ else if (isolated.trustedPnpmStore === undefined ||
1154
+ !pathWithin(isolated.trustedPnpmStore, resolved)) {
1155
+ throw new Error("pristine installation contains a symlink outside its repository and trusted store");
1156
+ }
1157
+ }
1158
+ else if (entry.isDirectory()) {
1159
+ if (path !== join(isolated.checkout, ".git"))
1160
+ await visit(path);
1161
+ }
1162
+ else if (entry.isFile() &&
1163
+ (directory.endsWith(`${sep}node_modules${sep}.bin`) ||
1164
+ entry.name === ".modules.yaml" ||
1165
+ entry.name === ".pnpm-workspace-state-v1.json")) {
1166
+ if ((await readFile(path, "utf8")).includes(isolated.checkout)) {
1167
+ relocatedFiles.push(relative(isolated.checkout, path));
1168
+ }
1169
+ }
1170
+ }
1171
+ };
1172
+ await visit(isolated.checkout);
1173
+ return relocatedFiles;
1174
+ };
1175
+ /** Installation reuse is bounded to these immutable commits and this owning Effect scope. */
1176
+ export const makeRepositoryPreparationCacheV1 = Effect.fn("RepositoryFoundry.makePreparationCache")(function* (input) {
1177
+ const repositoryRoot = yield* resolveRepositoryRoot(input.repositoryRoot);
1178
+ const recipes = input.trustedPreparationRecipes;
1179
+ const commits = new Set(input.commits);
1180
+ const eligible =
1181
+ // Both supported Unix runners materialize ordinary or copy-on-write file
1182
+ // copies, never hard links. Every repetition receives a private checkout
1183
+ // before candidate overlays are applied, while candidate commands are
1184
+ // forced onto the selected pnpm store in offline/frozen-store mode.
1185
+ (process.platform === "darwin" || process.platform === "linux") &&
1186
+ commits.size <= 2 &&
1187
+ [...commits].every((commit) => /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u.test(commit)) &&
1188
+ reusableFrozenInstall(recipes);
1189
+ const recipeIdentity = JSON.stringify(recipes);
1190
+ const cache = yield* ScopedCache.makeWith({
1191
+ capacity: Number.POSITIVE_INFINITY, // At most the two explicitly pinned historical commits.
1192
+ timeToLive: (exit) => (Exit.isSuccess(exit) ? "Infinity" : 0),
1193
+ lookup: Effect.fnUntraced(function* (commit) {
1194
+ const trustedPnpmStore = yield* resolveTrustedPnpmStore(recipes);
1195
+ const key = createHash("sha256")
1196
+ .update(JSON.stringify({
1197
+ version: 1,
1198
+ repositoryRoot,
1199
+ commit,
1200
+ recipes,
1201
+ node: process.version,
1202
+ executable: process.execPath,
1203
+ platform: process.platform,
1204
+ arch: process.arch,
1205
+ commandPath,
1206
+ corepackHome,
1207
+ trustedPnpmStore
1208
+ }))
1209
+ .digest("hex");
1210
+ const isolated = yield* acquireCheckout(repositoryRoot, commit, trustedPnpmStore, undefined, true);
1211
+ const preparationResults = yield* runRecipeSequence({
1212
+ isolated,
1213
+ commit,
1214
+ repetition: 0,
1215
+ stage: "trusted-preparation",
1216
+ recipes
1217
+ });
1218
+ if (preparationResults.some((result) => !commandPassed(result))) {
1219
+ return yield* failure("prepare-checkout", "pristine frozen installation failed; no reusable template was published", { templateKey: key, commit, trustedPreparationResults: preparationResults });
1220
+ }
1221
+ const relocatedFiles = yield* Effect.tryPromise({
1222
+ try: () => prepareTemplateRelocation(isolated),
1223
+ catch: (cause) => failure("prepare-checkout", "pristine installation cannot be safely relocated", cause)
1224
+ });
1225
+ return {
1226
+ isolated,
1227
+ key,
1228
+ preparationResults,
1229
+ relocatedFiles
1230
+ };
1231
+ })
1232
+ });
1233
+ const preparationCache = {
1234
+ get: (requested) => eligible &&
1235
+ requested.repositoryRoot === repositoryRoot &&
1236
+ commits.has(requested.commit) &&
1237
+ JSON.stringify(requested.recipes) === recipeIdentity
1238
+ ? ScopedCache.get(cache, requested.commit)
1239
+ : Effect.succeed(undefined)
1240
+ };
1241
+ return preparationCache;
1242
+ });
1243
+ const prepareCheckout = Effect.fn("RepositoryFoundry.prepareCommandCheckout")(function* (input) {
1244
+ if (input.patch !== undefined) {
1245
+ yield* applyCandidatePatch(input.checkout, input.patch);
1246
+ }
1247
+ yield* applyFileOverrides({
1248
+ checkout: input.checkout,
1249
+ overrides: input.fileOverrides
1250
+ });
1251
+ yield* applyOverlays({
1252
+ repositoryRoot: input.repositoryRoot,
1253
+ checkout: input.checkout,
1254
+ overlays: input.overlays
1255
+ });
1256
+ yield* applyContentOverlays({
1257
+ checkout: input.checkout,
1258
+ overlays: input.contentOverlays
1259
+ });
1260
+ });
1261
+ const resolveRepositoryRoot = (repositoryRoot) => Effect.tryPromise({
1262
+ try: () => realpath(repositoryRoot),
1263
+ catch: (cause) => failure("resolve-repository", `failed to resolve ${JSON.stringify(repositoryRoot)}`, cause)
1264
+ });
1265
+ export const replayRepositoryCommandsV1 = Effect.fn("RepositoryFoundry.replayRepositoryCommands")(function* (input) {
1266
+ const reconstruction = yield* Effect.serviceOption(RepositoryFoundryEvidenceReconstruction);
1267
+ if (Option.isSome(reconstruction))
1268
+ return yield* reconstruction.value.commands(input);
1269
+ if (input.recipes.length === 0 ||
1270
+ new Set(input.recipes.map((recipe) => recipe.id)).size !== input.recipes.length) {
1271
+ return yield* failure("run-command", "repository replay requires one or more uniquely identified grade recipes");
1272
+ }
1273
+ const root = yield* resolveRepositoryRoot(input.repositoryRoot);
1274
+ const template = input.preparationCache === undefined
1275
+ ? undefined
1276
+ : yield* input.preparationCache.get({
1277
+ repositoryRoot: root,
1278
+ commit: input.commit,
1279
+ recipes: input.trustedPreparationRecipes ?? []
1280
+ });
1281
+ const trustedPnpmStore = template === undefined
1282
+ ? yield* resolveTrustedPnpmStore(input.trustedPreparationRecipes ?? [])
1283
+ : template.isolated.trustedPnpmStore;
1284
+ const repetitions = Math.max(1, Math.min(input.repetitions ?? 2, 5));
1285
+ const repetitionStart = Math.max(1, Math.min(Math.trunc(input.repetitionStart ?? 1), 5));
1286
+ const results = [];
1287
+ for (let repetition = repetitionStart; repetition < repetitionStart + repetitions; repetition += 1) {
1288
+ results.push(...(yield* Effect.scoped(Effect.gen(function* () {
1289
+ const isolated = yield* acquireCheckout(root, input.commit, trustedPnpmStore, template);
1290
+ const checkout = isolated.checkout;
1291
+ const preparation = template === undefined
1292
+ ? undefined
1293
+ : {
1294
+ kind: "pristine-install-template",
1295
+ templateKey: template.key,
1296
+ sourceCommit: input.commit,
1297
+ cloneDurationMs: isolated.materializationDurationMs,
1298
+ sharedResults: template.preparationResults
1299
+ };
1300
+ const trustedPreparationResults = template === undefined
1301
+ ? yield* runRecipeSequence({
1302
+ isolated,
1303
+ commit: input.commit,
1304
+ repetition,
1305
+ stage: "trusted-preparation",
1306
+ recipes: input.trustedPreparationRecipes ?? []
1307
+ })
1308
+ : [];
1309
+ const failedPreparation = trustedPreparationResults.find((result) => !commandPassed(result));
1310
+ if (failedPreparation !== undefined) {
1311
+ return [
1312
+ terminalResult(failedPreparation, {
1313
+ gradeExecuted: false,
1314
+ trustedPreparationResults,
1315
+ candidateValidationResults: [],
1316
+ preparation,
1317
+ isolationTimings: isolated.timings
1318
+ })
1319
+ ];
1320
+ }
1321
+ const overlayStartedAt = Date.now();
1322
+ yield* prepareCheckout({
1323
+ repositoryRoot: root,
1324
+ checkout,
1325
+ ...(input.patch === undefined ? {} : { patch: input.patch }),
1326
+ overlays: input.overlays ?? [],
1327
+ contentOverlays: input.contentOverlays ?? [],
1328
+ fileOverrides: input.fileOverrides ?? []
1329
+ });
1330
+ isolated.timings.overlayDurationMs = Date.now() - overlayStartedAt;
1331
+ const candidateValidationResults = yield* runRecipeSequence({
1332
+ isolated,
1333
+ commit: input.commit,
1334
+ repetition,
1335
+ stage: "candidate-validation",
1336
+ recipes: input.candidateValidationRecipes ?? []
1337
+ });
1338
+ const failedValidation = candidateValidationResults.find((result) => !commandPassed(result));
1339
+ if (failedValidation !== undefined) {
1340
+ return [
1341
+ terminalResult(failedValidation, {
1342
+ gradeExecuted: false,
1343
+ trustedPreparationResults,
1344
+ candidateValidationResults,
1345
+ preparation,
1346
+ isolationTimings: isolated.timings
1347
+ })
1348
+ ];
1349
+ }
1350
+ const grades = [];
1351
+ for (const recipe of input.recipes) {
1352
+ const grade = yield* runCommand({
1353
+ isolated,
1354
+ commit: input.commit,
1355
+ repetition,
1356
+ stage: "grade",
1357
+ recipe
1358
+ });
1359
+ grades.push(terminalResult(grade, {
1360
+ gradeExecuted: true,
1361
+ trustedPreparationResults,
1362
+ candidateValidationResults,
1363
+ preparation,
1364
+ isolationTimings: isolated.timings
1365
+ }));
1366
+ // A killed process or incomplete capture cannot yield a grade.
1367
+ // Retain it, release this checkout, and leave queued work unstarted.
1368
+ if (repositoryCommandExecutionIncompleteV1(grade) &&
1369
+ input.continueAfterIncompleteGrade !== true)
1370
+ break;
1371
+ }
1372
+ return grades;
1373
+ }))));
1374
+ }
1375
+ return results;
1376
+ });
1377
+ export const replayRepositoryCommandV1 = Effect.fn("RepositoryFoundry.replayRepositoryCommand")(function* (input) {
1378
+ return yield* replayRepositoryCommandsV1({
1379
+ repositoryRoot: input.repositoryRoot,
1380
+ commit: input.commit,
1381
+ recipes: [input.recipe],
1382
+ ...(input.repetitions === undefined ? {} : { repetitions: input.repetitions }),
1383
+ ...(input.overlays === undefined ? {} : { overlays: input.overlays }),
1384
+ ...(input.contentOverlays === undefined ? {} : { contentOverlays: input.contentOverlays }),
1385
+ ...(input.fileOverrides === undefined ? {} : { fileOverrides: input.fileOverrides }),
1386
+ ...(input.patch === undefined ? {} : { patch: input.patch }),
1387
+ ...(input.trustedPreparationRecipes === undefined
1388
+ ? {}
1389
+ : {
1390
+ trustedPreparationRecipes: input.trustedPreparationRecipes
1391
+ }),
1392
+ ...(input.candidateValidationRecipes === undefined
1393
+ ? {}
1394
+ : {
1395
+ candidateValidationRecipes: input.candidateValidationRecipes
1396
+ })
1397
+ });
1398
+ });
1399
+ const inspectPath = async (checkout, repositoryPath) => {
1400
+ if (repositoryPath.trim().length === 0 ||
1401
+ isAbsolute(repositoryPath) ||
1402
+ repositoryPath.includes("\\") ||
1403
+ repositoryPath.includes("\0") ||
1404
+ repositoryPath.split("/").some((part) => part.length === 0 || part === "." || part === "..")) {
1405
+ throw new Error(`inspection path is not a canonical repository-relative path: ${JSON.stringify(repositoryPath)}`);
1406
+ }
1407
+ const destination = resolve(checkout, repositoryPath);
1408
+ if (!pathWithin(checkout, destination)) {
1409
+ throw new Error(`inspection path escapes checkout: ${JSON.stringify(repositoryPath)}`);
1410
+ }
1411
+ let current = checkout;
1412
+ const parts = repositoryPath.split("/");
1413
+ for (let index = 0; index < parts.length; index += 1) {
1414
+ current = join(current, parts[index]);
1415
+ let metadata;
1416
+ try {
1417
+ metadata = await lstat(current);
1418
+ }
1419
+ catch (cause) {
1420
+ if (cause !== null &&
1421
+ typeof cause === "object" &&
1422
+ "code" in cause &&
1423
+ cause.code === "ENOENT") {
1424
+ return "missing";
1425
+ }
1426
+ throw cause;
1427
+ }
1428
+ const final = index === parts.length - 1;
1429
+ if (metadata.isSymbolicLink()) {
1430
+ if (final)
1431
+ return "symlink";
1432
+ throw new Error(`inspection path traverses a symbolic link: ${JSON.stringify(repositoryPath)}`);
1433
+ }
1434
+ if (!final && !metadata.isDirectory())
1435
+ return "missing";
1436
+ if (final) {
1437
+ if (metadata.isFile())
1438
+ return "file";
1439
+ if (metadata.isDirectory())
1440
+ return "directory";
1441
+ return "missing";
1442
+ }
1443
+ }
1444
+ return "missing";
1445
+ };
1446
+ export const inspectRepositorySolutionPathsV1 = Effect.fn("RepositoryFoundry.inspectRepositorySolutionPaths")(function* (input) {
1447
+ if (input.paths.length === 0) {
1448
+ return yield* failure("prepare-checkout", "repository path inspection requires at least one path");
1449
+ }
1450
+ const paths = [...new Set(input.paths)];
1451
+ const root = yield* resolveRepositoryRoot(input.repositoryRoot);
1452
+ const repetitions = Math.max(1, Math.min(input.repetitions ?? 2, 5));
1453
+ const results = [];
1454
+ for (let repetition = 1; repetition <= repetitions; repetition += 1) {
1455
+ results.push(...(yield* Effect.scoped(Effect.gen(function* () {
1456
+ const isolated = yield* acquireCheckout(root, input.commit);
1457
+ const checkout = isolated.checkout;
1458
+ yield* prepareCheckout({
1459
+ repositoryRoot: root,
1460
+ checkout,
1461
+ ...(input.patch === undefined ? {} : { patch: input.patch }),
1462
+ overlays: [],
1463
+ contentOverlays: [],
1464
+ fileOverrides: input.fileOverrides ?? []
1465
+ });
1466
+ const inspected = [];
1467
+ for (const repositoryPath of paths) {
1468
+ const kind = yield* Effect.tryPromise({
1469
+ try: () => inspectPath(checkout, repositoryPath),
1470
+ catch: (cause) => failure("prepare-checkout", cause instanceof Error ? cause.message : String(cause), cause)
1471
+ });
1472
+ inspected.push({
1473
+ commit: input.commit,
1474
+ repetition,
1475
+ path: repositoryPath,
1476
+ kind
1477
+ });
1478
+ }
1479
+ return inspected;
1480
+ }))));
1481
+ }
1482
+ return results;
1483
+ });