@prismatic-io/lux 0.0.1

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 (636) hide show
  1. package/LICENSE +21 -0
  2. package/lib/answerers/claude-code/index.d.ts +17 -0
  3. package/lib/answerers/claude-code/index.d.ts.map +1 -0
  4. package/lib/answerers/claude-code/index.js +280 -0
  5. package/lib/answerers/claude-code/index.js.map +1 -0
  6. package/lib/answerers/persona/index.d.ts +40 -0
  7. package/lib/answerers/persona/index.d.ts.map +1 -0
  8. package/lib/answerers/persona/index.js +265 -0
  9. package/lib/answerers/persona/index.js.map +1 -0
  10. package/lib/answerers/scripted/index.d.ts +8 -0
  11. package/lib/answerers/scripted/index.d.ts.map +1 -0
  12. package/lib/answerers/scripted/index.js +163 -0
  13. package/lib/answerers/scripted/index.js.map +1 -0
  14. package/lib/answerers/shared.d.ts +13 -0
  15. package/lib/answerers/shared.d.ts.map +1 -0
  16. package/lib/answerers/shared.js +17 -0
  17. package/lib/answerers/shared.js.map +1 -0
  18. package/lib/answerers/terminal/index.d.ts +9 -0
  19. package/lib/answerers/terminal/index.d.ts.map +1 -0
  20. package/lib/answerers/terminal/index.js +97 -0
  21. package/lib/answerers/terminal/index.js.map +1 -0
  22. package/lib/assertions/core/command-exits-zero.d.ts +21 -0
  23. package/lib/assertions/core/command-exits-zero.d.ts.map +1 -0
  24. package/lib/assertions/core/command-exits-zero.js +87 -0
  25. package/lib/assertions/core/command-exits-zero.js.map +1 -0
  26. package/lib/assertions/core/event-checks.d.ts +57 -0
  27. package/lib/assertions/core/event-checks.d.ts.map +1 -0
  28. package/lib/assertions/core/event-checks.js +123 -0
  29. package/lib/assertions/core/event-checks.js.map +1 -0
  30. package/lib/assertions/core/file-checks.d.ts +103 -0
  31. package/lib/assertions/core/file-checks.d.ts.map +1 -0
  32. package/lib/assertions/core/file-checks.js +288 -0
  33. package/lib/assertions/core/file-checks.js.map +1 -0
  34. package/lib/assertions/core/helpers.d.ts +66 -0
  35. package/lib/assertions/core/helpers.d.ts.map +1 -0
  36. package/lib/assertions/core/helpers.js +170 -0
  37. package/lib/assertions/core/helpers.js.map +1 -0
  38. package/lib/assertions/core/index.d.ts +15 -0
  39. package/lib/assertions/core/index.d.ts.map +1 -0
  40. package/lib/assertions/core/index.js +56 -0
  41. package/lib/assertions/core/index.js.map +1 -0
  42. package/lib/assertions/core/json-pointer-equals.d.ts +19 -0
  43. package/lib/assertions/core/json-pointer-equals.d.ts.map +1 -0
  44. package/lib/assertions/core/json-pointer-equals.js +71 -0
  45. package/lib/assertions/core/json-pointer-equals.js.map +1 -0
  46. package/lib/assertions/core/output-checks.d.ts +46 -0
  47. package/lib/assertions/core/output-checks.d.ts.map +1 -0
  48. package/lib/assertions/core/output-checks.js +121 -0
  49. package/lib/assertions/core/output-checks.js.map +1 -0
  50. package/lib/assertions/core/predicate.d.ts +14 -0
  51. package/lib/assertions/core/predicate.d.ts.map +1 -0
  52. package/lib/assertions/core/predicate.js +55 -0
  53. package/lib/assertions/core/predicate.js.map +1 -0
  54. package/lib/assertions/core/resource-checks.d.ts +71 -0
  55. package/lib/assertions/core/resource-checks.d.ts.map +1 -0
  56. package/lib/assertions/core/resource-checks.js +159 -0
  57. package/lib/assertions/core/resource-checks.js.map +1 -0
  58. package/lib/assertions/core/safe-regex.d.ts +23 -0
  59. package/lib/assertions/core/safe-regex.d.ts.map +1 -0
  60. package/lib/assertions/core/safe-regex.js +102 -0
  61. package/lib/assertions/core/safe-regex.js.map +1 -0
  62. package/lib/assertions/core/tool-called-with.d.ts +21 -0
  63. package/lib/assertions/core/tool-called-with.d.ts.map +1 -0
  64. package/lib/assertions/core/tool-called-with.js +69 -0
  65. package/lib/assertions/core/tool-called-with.js.map +1 -0
  66. package/lib/assertions/core/tool-calls.d.ts +22 -0
  67. package/lib/assertions/core/tool-calls.d.ts.map +1 -0
  68. package/lib/assertions/core/tool-calls.js +17 -0
  69. package/lib/assertions/core/tool-calls.js.map +1 -0
  70. package/lib/assertions/core/tool-invocation.d.ts +36 -0
  71. package/lib/assertions/core/tool-invocation.d.ts.map +1 -0
  72. package/lib/assertions/core/tool-invocation.js +170 -0
  73. package/lib/assertions/core/tool-invocation.js.map +1 -0
  74. package/lib/assertions/core/tool-result-checks.d.ts +59 -0
  75. package/lib/assertions/core/tool-result-checks.d.ts.map +1 -0
  76. package/lib/assertions/core/tool-result-checks.js +193 -0
  77. package/lib/assertions/core/tool-result-checks.js.map +1 -0
  78. package/lib/assertions/mcp/discovery-checks.d.ts +36 -0
  79. package/lib/assertions/mcp/discovery-checks.d.ts.map +1 -0
  80. package/lib/assertions/mcp/discovery-checks.js +122 -0
  81. package/lib/assertions/mcp/discovery-checks.js.map +1 -0
  82. package/lib/assertions/mcp/index.d.ts +10 -0
  83. package/lib/assertions/mcp/index.d.ts.map +1 -0
  84. package/lib/assertions/mcp/index.js +25 -0
  85. package/lib/assertions/mcp/index.js.map +1 -0
  86. package/lib/assertions/rubric/index.d.ts +104 -0
  87. package/lib/assertions/rubric/index.d.ts.map +1 -0
  88. package/lib/assertions/rubric/index.js +191 -0
  89. package/lib/assertions/rubric/index.js.map +1 -0
  90. package/lib/assertions/rubric/internal.d.ts +14 -0
  91. package/lib/assertions/rubric/internal.d.ts.map +1 -0
  92. package/lib/assertions/rubric/internal.js +335 -0
  93. package/lib/assertions/rubric/internal.js.map +1 -0
  94. package/lib/authoring.d.ts +3 -0
  95. package/lib/authoring.d.ts.map +1 -0
  96. package/lib/authoring.js +3 -0
  97. package/lib/authoring.js.map +1 -0
  98. package/lib/cli/bin.d.ts +3 -0
  99. package/lib/cli/bin.d.ts.map +1 -0
  100. package/lib/cli/bin.js +15 -0
  101. package/lib/cli/bin.js.map +1 -0
  102. package/lib/cli/command-runtime.d.ts +5 -0
  103. package/lib/cli/command-runtime.d.ts.map +1 -0
  104. package/lib/cli/command-runtime.js +30 -0
  105. package/lib/cli/command-runtime.js.map +1 -0
  106. package/lib/cli/init.d.ts +21 -0
  107. package/lib/cli/init.d.ts.map +1 -0
  108. package/lib/cli/init.js +340 -0
  109. package/lib/cli/init.js.map +1 -0
  110. package/lib/cli/program.d.ts +22 -0
  111. package/lib/cli/program.d.ts.map +1 -0
  112. package/lib/cli/program.js +757 -0
  113. package/lib/cli/program.js.map +1 -0
  114. package/lib/cli/render/format.d.ts +14 -0
  115. package/lib/cli/render/format.d.ts.map +1 -0
  116. package/lib/cli/render/format.js +71 -0
  117. package/lib/cli/render/format.js.map +1 -0
  118. package/lib/cli/render/reporter.d.ts +73 -0
  119. package/lib/cli/render/reporter.d.ts.map +1 -0
  120. package/lib/cli/render/reporter.js +519 -0
  121. package/lib/cli/render/reporter.js.map +1 -0
  122. package/lib/cli/render/tty.d.ts +77 -0
  123. package/lib/cli/render/tty.d.ts.map +1 -0
  124. package/lib/cli/render/tty.js +283 -0
  125. package/lib/cli/render/tty.js.map +1 -0
  126. package/lib/cli/run-options.d.ts +27 -0
  127. package/lib/cli/run-options.d.ts.map +1 -0
  128. package/lib/cli/run-options.js +58 -0
  129. package/lib/cli/run-options.js.map +1 -0
  130. package/lib/cli/skills.d.ts +38 -0
  131. package/lib/cli/skills.d.ts.map +1 -0
  132. package/lib/cli/skills.js +84 -0
  133. package/lib/cli/skills.js.map +1 -0
  134. package/lib/core/annotation.d.ts +56 -0
  135. package/lib/core/annotation.d.ts.map +1 -0
  136. package/lib/core/annotation.js +92 -0
  137. package/lib/core/annotation.js.map +1 -0
  138. package/lib/core/answerer.d.ts +68 -0
  139. package/lib/core/answerer.d.ts.map +1 -0
  140. package/lib/core/answerer.js +17 -0
  141. package/lib/core/answerer.js.map +1 -0
  142. package/lib/core/artifact-evidence.d.ts +50 -0
  143. package/lib/core/artifact-evidence.d.ts.map +1 -0
  144. package/lib/core/artifact-evidence.js +185 -0
  145. package/lib/core/artifact-evidence.js.map +1 -0
  146. package/lib/core/artifact-snapshot.d.ts +37 -0
  147. package/lib/core/artifact-snapshot.d.ts.map +1 -0
  148. package/lib/core/artifact-snapshot.js +339 -0
  149. package/lib/core/artifact-snapshot.js.map +1 -0
  150. package/lib/core/assertion.d.ts +129 -0
  151. package/lib/core/assertion.d.ts.map +1 -0
  152. package/lib/core/assertion.js +75 -0
  153. package/lib/core/assertion.js.map +1 -0
  154. package/lib/core/case.d.ts +79 -0
  155. package/lib/core/case.d.ts.map +1 -0
  156. package/lib/core/case.js +90 -0
  157. package/lib/core/case.js.map +1 -0
  158. package/lib/core/conversation-turn.d.ts +23 -0
  159. package/lib/core/conversation-turn.d.ts.map +1 -0
  160. package/lib/core/conversation-turn.js +31 -0
  161. package/lib/core/conversation-turn.js.map +1 -0
  162. package/lib/core/driver.d.ts +64 -0
  163. package/lib/core/driver.d.ts.map +1 -0
  164. package/lib/core/driver.js +18 -0
  165. package/lib/core/driver.js.map +1 -0
  166. package/lib/core/emitter.d.ts +20 -0
  167. package/lib/core/emitter.d.ts.map +1 -0
  168. package/lib/core/emitter.js +34 -0
  169. package/lib/core/emitter.js.map +1 -0
  170. package/lib/core/events.d.ts +202 -0
  171. package/lib/core/events.d.ts.map +1 -0
  172. package/lib/core/events.js +107 -0
  173. package/lib/core/events.js.map +1 -0
  174. package/lib/core/exec.d.ts +82 -0
  175. package/lib/core/exec.d.ts.map +1 -0
  176. package/lib/core/exec.js +168 -0
  177. package/lib/core/exec.js.map +1 -0
  178. package/lib/core/experiment.d.ts +285 -0
  179. package/lib/core/experiment.d.ts.map +1 -0
  180. package/lib/core/experiment.js +389 -0
  181. package/lib/core/experiment.js.map +1 -0
  182. package/lib/core/glob.d.ts +3 -0
  183. package/lib/core/glob.d.ts.map +1 -0
  184. package/lib/core/glob.js +32 -0
  185. package/lib/core/glob.js.map +1 -0
  186. package/lib/core/hash.d.ts +8 -0
  187. package/lib/core/hash.d.ts.map +1 -0
  188. package/lib/core/hash.js +214 -0
  189. package/lib/core/hash.js.map +1 -0
  190. package/lib/core/index.d.ts +29 -0
  191. package/lib/core/index.d.ts.map +1 -0
  192. package/lib/core/index.js +29 -0
  193. package/lib/core/index.js.map +1 -0
  194. package/lib/core/lifecycle-fixtures.d.ts +20 -0
  195. package/lib/core/lifecycle-fixtures.d.ts.map +1 -0
  196. package/lib/core/lifecycle-fixtures.js +109 -0
  197. package/lib/core/lifecycle-fixtures.js.map +1 -0
  198. package/lib/core/lifecycle-hooks.d.ts +52 -0
  199. package/lib/core/lifecycle-hooks.d.ts.map +1 -0
  200. package/lib/core/lifecycle-hooks.js +42 -0
  201. package/lib/core/lifecycle-hooks.js.map +1 -0
  202. package/lib/core/mcp-events.d.ts +44 -0
  203. package/lib/core/mcp-events.d.ts.map +1 -0
  204. package/lib/core/mcp-events.js +23 -0
  205. package/lib/core/mcp-events.js.map +1 -0
  206. package/lib/core/model-cli.d.ts +57 -0
  207. package/lib/core/model-cli.d.ts.map +1 -0
  208. package/lib/core/model-cli.js +237 -0
  209. package/lib/core/model-cli.js.map +1 -0
  210. package/lib/core/observer.d.ts +31 -0
  211. package/lib/core/observer.d.ts.map +1 -0
  212. package/lib/core/observer.js +25 -0
  213. package/lib/core/observer.js.map +1 -0
  214. package/lib/core/platform-process.d.ts +33 -0
  215. package/lib/core/platform-process.d.ts.map +1 -0
  216. package/lib/core/platform-process.js +214 -0
  217. package/lib/core/platform-process.js.map +1 -0
  218. package/lib/core/registry.d.ts +22 -0
  219. package/lib/core/registry.d.ts.map +1 -0
  220. package/lib/core/registry.js +45 -0
  221. package/lib/core/registry.js.map +1 -0
  222. package/lib/core/rubric.d.ts +18 -0
  223. package/lib/core/rubric.d.ts.map +1 -0
  224. package/lib/core/rubric.js +49 -0
  225. package/lib/core/rubric.js.map +1 -0
  226. package/lib/core/run.d.ts +419 -0
  227. package/lib/core/run.d.ts.map +1 -0
  228. package/lib/core/run.js +126 -0
  229. package/lib/core/run.js.map +1 -0
  230. package/lib/core/statistics.d.ts +39 -0
  231. package/lib/core/statistics.d.ts.map +1 -0
  232. package/lib/core/statistics.js +163 -0
  233. package/lib/core/statistics.js.map +1 -0
  234. package/lib/core/subject.d.ts +4 -0
  235. package/lib/core/subject.d.ts.map +1 -0
  236. package/lib/core/subject.js +46 -0
  237. package/lib/core/subject.js.map +1 -0
  238. package/lib/core/tool-events.d.ts +69 -0
  239. package/lib/core/tool-events.d.ts.map +1 -0
  240. package/lib/core/tool-events.js +242 -0
  241. package/lib/core/tool-events.js.map +1 -0
  242. package/lib/core/usage.d.ts +78 -0
  243. package/lib/core/usage.d.ts.map +1 -0
  244. package/lib/core/usage.js +104 -0
  245. package/lib/core/usage.js.map +1 -0
  246. package/lib/core/wire.d.ts +83 -0
  247. package/lib/core/wire.d.ts.map +1 -0
  248. package/lib/core/wire.js +101 -0
  249. package/lib/core/wire.js.map +1 -0
  250. package/lib/core/zod.d.ts +10 -0
  251. package/lib/core/zod.d.ts.map +1 -0
  252. package/lib/core/zod.js +18 -0
  253. package/lib/core/zod.js.map +1 -0
  254. package/lib/drivers/claude-code/index.d.ts +55 -0
  255. package/lib/drivers/claude-code/index.d.ts.map +1 -0
  256. package/lib/drivers/claude-code/index.js +779 -0
  257. package/lib/drivers/claude-code/index.js.map +1 -0
  258. package/lib/drivers/claude-code/parse-events.d.ts +46 -0
  259. package/lib/drivers/claude-code/parse-events.d.ts.map +1 -0
  260. package/lib/drivers/claude-code/parse-events.js +387 -0
  261. package/lib/drivers/claude-code/parse-events.js.map +1 -0
  262. package/lib/drivers/codex/app-events.d.ts +16 -0
  263. package/lib/drivers/codex/app-events.d.ts.map +1 -0
  264. package/lib/drivers/codex/app-events.js +184 -0
  265. package/lib/drivers/codex/app-events.js.map +1 -0
  266. package/lib/drivers/codex/config.d.ts +55 -0
  267. package/lib/drivers/codex/config.d.ts.map +1 -0
  268. package/lib/drivers/codex/config.js +125 -0
  269. package/lib/drivers/codex/config.js.map +1 -0
  270. package/lib/drivers/codex/index.d.ts +8 -0
  271. package/lib/drivers/codex/index.d.ts.map +1 -0
  272. package/lib/drivers/codex/index.js +904 -0
  273. package/lib/drivers/codex/index.js.map +1 -0
  274. package/lib/drivers/mcp/index.d.ts +52 -0
  275. package/lib/drivers/mcp/index.d.ts.map +1 -0
  276. package/lib/drivers/mcp/index.js +282 -0
  277. package/lib/drivers/mcp/index.js.map +1 -0
  278. package/lib/drivers/mcp/transport.d.ts +50 -0
  279. package/lib/drivers/mcp/transport.d.ts.map +1 -0
  280. package/lib/drivers/mcp/transport.js +190 -0
  281. package/lib/drivers/mcp/transport.js.map +1 -0
  282. package/lib/drivers/shared/artifacts.d.ts +4 -0
  283. package/lib/drivers/shared/artifacts.d.ts.map +1 -0
  284. package/lib/drivers/shared/artifacts.js +81 -0
  285. package/lib/drivers/shared/artifacts.js.map +1 -0
  286. package/lib/drivers/shared/secure-copy.d.ts +2 -0
  287. package/lib/drivers/shared/secure-copy.d.ts.map +1 -0
  288. package/lib/drivers/shared/secure-copy.js +35 -0
  289. package/lib/drivers/shared/secure-copy.js.map +1 -0
  290. package/lib/drivers/subprocess/index.d.ts +24 -0
  291. package/lib/drivers/subprocess/index.d.ts.map +1 -0
  292. package/lib/drivers/subprocess/index.js +451 -0
  293. package/lib/drivers/subprocess/index.js.map +1 -0
  294. package/lib/index.d.ts +40 -0
  295. package/lib/index.d.ts.map +1 -0
  296. package/lib/index.js +48 -0
  297. package/lib/index.js.map +1 -0
  298. package/lib/optimization/reflective.d.ts +27 -0
  299. package/lib/optimization/reflective.d.ts.map +1 -0
  300. package/lib/optimization/reflective.js +257 -0
  301. package/lib/optimization/reflective.js.map +1 -0
  302. package/lib/orchestrator/annotation-loader.d.ts +65 -0
  303. package/lib/orchestrator/annotation-loader.d.ts.map +1 -0
  304. package/lib/orchestrator/annotation-loader.js +73 -0
  305. package/lib/orchestrator/annotation-loader.js.map +1 -0
  306. package/lib/orchestrator/annotation-store.d.ts +3 -0
  307. package/lib/orchestrator/annotation-store.d.ts.map +1 -0
  308. package/lib/orchestrator/annotation-store.js +100 -0
  309. package/lib/orchestrator/annotation-store.js.map +1 -0
  310. package/lib/orchestrator/authored-dependencies.d.ts +3 -0
  311. package/lib/orchestrator/authored-dependencies.d.ts.map +1 -0
  312. package/lib/orchestrator/authored-dependencies.js +146 -0
  313. package/lib/orchestrator/authored-dependencies.js.map +1 -0
  314. package/lib/orchestrator/campaign-lifecycle.d.ts +105 -0
  315. package/lib/orchestrator/campaign-lifecycle.d.ts.map +1 -0
  316. package/lib/orchestrator/campaign-lifecycle.js +141 -0
  317. package/lib/orchestrator/campaign-lifecycle.js.map +1 -0
  318. package/lib/orchestrator/candidate-integrity.d.ts +4 -0
  319. package/lib/orchestrator/candidate-integrity.d.ts.map +1 -0
  320. package/lib/orchestrator/candidate-integrity.js +14 -0
  321. package/lib/orchestrator/candidate-integrity.js.map +1 -0
  322. package/lib/orchestrator/candidates.d.ts +13 -0
  323. package/lib/orchestrator/candidates.d.ts.map +1 -0
  324. package/lib/orchestrator/candidates.js +577 -0
  325. package/lib/orchestrator/candidates.js.map +1 -0
  326. package/lib/orchestrator/compare.d.ts +80 -0
  327. package/lib/orchestrator/compare.d.ts.map +1 -0
  328. package/lib/orchestrator/compare.js +270 -0
  329. package/lib/orchestrator/compare.js.map +1 -0
  330. package/lib/orchestrator/comparison-identity.d.ts +10 -0
  331. package/lib/orchestrator/comparison-identity.d.ts.map +1 -0
  332. package/lib/orchestrator/comparison-identity.js +39 -0
  333. package/lib/orchestrator/comparison-identity.js.map +1 -0
  334. package/lib/orchestrator/config.d.ts +121 -0
  335. package/lib/orchestrator/config.d.ts.map +1 -0
  336. package/lib/orchestrator/config.js +176 -0
  337. package/lib/orchestrator/config.js.map +1 -0
  338. package/lib/orchestrator/content-bound-json.d.ts +5 -0
  339. package/lib/orchestrator/content-bound-json.d.ts.map +1 -0
  340. package/lib/orchestrator/content-bound-json.js +59 -0
  341. package/lib/orchestrator/content-bound-json.js.map +1 -0
  342. package/lib/orchestrator/discover.d.ts +45 -0
  343. package/lib/orchestrator/discover.d.ts.map +1 -0
  344. package/lib/orchestrator/discover.js +98 -0
  345. package/lib/orchestrator/discover.js.map +1 -0
  346. package/lib/orchestrator/doctor.d.ts +141 -0
  347. package/lib/orchestrator/doctor.d.ts.map +1 -0
  348. package/lib/orchestrator/doctor.js +525 -0
  349. package/lib/orchestrator/doctor.js.map +1 -0
  350. package/lib/orchestrator/driver-session.d.ts +177 -0
  351. package/lib/orchestrator/driver-session.d.ts.map +1 -0
  352. package/lib/orchestrator/driver-session.js +396 -0
  353. package/lib/orchestrator/driver-session.js.map +1 -0
  354. package/lib/orchestrator/evaluator-identity.d.ts +7 -0
  355. package/lib/orchestrator/evaluator-identity.d.ts.map +1 -0
  356. package/lib/orchestrator/evaluator-identity.js +42 -0
  357. package/lib/orchestrator/evaluator-identity.js.map +1 -0
  358. package/lib/orchestrator/experiment-budget.d.ts +11 -0
  359. package/lib/orchestrator/experiment-budget.d.ts.map +1 -0
  360. package/lib/orchestrator/experiment-budget.js +51 -0
  361. package/lib/orchestrator/experiment-budget.js.map +1 -0
  362. package/lib/orchestrator/experiment-context.d.ts +43 -0
  363. package/lib/orchestrator/experiment-context.d.ts.map +1 -0
  364. package/lib/orchestrator/experiment-context.js +9 -0
  365. package/lib/orchestrator/experiment-context.js.map +1 -0
  366. package/lib/orchestrator/experiment-contracts.d.ts +290 -0
  367. package/lib/orchestrator/experiment-contracts.d.ts.map +1 -0
  368. package/lib/orchestrator/experiment-contracts.js +38 -0
  369. package/lib/orchestrator/experiment-contracts.js.map +1 -0
  370. package/lib/orchestrator/experiment-corpus.d.ts +21 -0
  371. package/lib/orchestrator/experiment-corpus.d.ts.map +1 -0
  372. package/lib/orchestrator/experiment-corpus.js +276 -0
  373. package/lib/orchestrator/experiment-corpus.js.map +1 -0
  374. package/lib/orchestrator/experiment-evaluation.d.ts +44 -0
  375. package/lib/orchestrator/experiment-evaluation.d.ts.map +1 -0
  376. package/lib/orchestrator/experiment-evaluation.js +542 -0
  377. package/lib/orchestrator/experiment-evaluation.js.map +1 -0
  378. package/lib/orchestrator/experiment-finish.d.ts +6 -0
  379. package/lib/orchestrator/experiment-finish.d.ts.map +1 -0
  380. package/lib/orchestrator/experiment-finish.js +19 -0
  381. package/lib/orchestrator/experiment-finish.js.map +1 -0
  382. package/lib/orchestrator/experiment-identity.d.ts +6 -0
  383. package/lib/orchestrator/experiment-identity.d.ts.map +1 -0
  384. package/lib/orchestrator/experiment-identity.js +257 -0
  385. package/lib/orchestrator/experiment-identity.js.map +1 -0
  386. package/lib/orchestrator/experiment-loader.d.ts +175 -0
  387. package/lib/orchestrator/experiment-loader.d.ts.map +1 -0
  388. package/lib/orchestrator/experiment-loader.js +77 -0
  389. package/lib/orchestrator/experiment-loader.js.map +1 -0
  390. package/lib/orchestrator/experiment-report.d.ts +450 -0
  391. package/lib/orchestrator/experiment-report.d.ts.map +1 -0
  392. package/lib/orchestrator/experiment-report.js +570 -0
  393. package/lib/orchestrator/experiment-report.js.map +1 -0
  394. package/lib/orchestrator/experiment-runs.d.ts +379 -0
  395. package/lib/orchestrator/experiment-runs.d.ts.map +1 -0
  396. package/lib/orchestrator/experiment-runs.js +796 -0
  397. package/lib/orchestrator/experiment-runs.js.map +1 -0
  398. package/lib/orchestrator/experiment-runtime-identity.d.ts +41 -0
  399. package/lib/orchestrator/experiment-runtime-identity.d.ts.map +1 -0
  400. package/lib/orchestrator/experiment-runtime-identity.js +411 -0
  401. package/lib/orchestrator/experiment-runtime-identity.js.map +1 -0
  402. package/lib/orchestrator/experiment-selection.d.ts +25 -0
  403. package/lib/orchestrator/experiment-selection.d.ts.map +1 -0
  404. package/lib/orchestrator/experiment-selection.js +286 -0
  405. package/lib/orchestrator/experiment-selection.js.map +1 -0
  406. package/lib/orchestrator/experiment.d.ts +11 -0
  407. package/lib/orchestrator/experiment.d.ts.map +1 -0
  408. package/lib/orchestrator/experiment.js +639 -0
  409. package/lib/orchestrator/experiment.js.map +1 -0
  410. package/lib/orchestrator/fixture-identity.d.ts +7 -0
  411. package/lib/orchestrator/fixture-identity.d.ts.map +1 -0
  412. package/lib/orchestrator/fixture-identity.js +55 -0
  413. package/lib/orchestrator/fixture-identity.js.map +1 -0
  414. package/lib/orchestrator/fs-read.d.ts +19 -0
  415. package/lib/orchestrator/fs-read.d.ts.map +1 -0
  416. package/lib/orchestrator/fs-read.js +113 -0
  417. package/lib/orchestrator/fs-read.js.map +1 -0
  418. package/lib/orchestrator/grader-experiment.d.ts +190 -0
  419. package/lib/orchestrator/grader-experiment.d.ts.map +1 -0
  420. package/lib/orchestrator/grader-experiment.js +579 -0
  421. package/lib/orchestrator/grader-experiment.js.map +1 -0
  422. package/lib/orchestrator/grading.d.ts +31 -0
  423. package/lib/orchestrator/grading.d.ts.map +1 -0
  424. package/lib/orchestrator/grading.js +186 -0
  425. package/lib/orchestrator/grading.js.map +1 -0
  426. package/lib/orchestrator/index.d.ts +22 -0
  427. package/lib/orchestrator/index.d.ts.map +1 -0
  428. package/lib/orchestrator/index.js +30 -0
  429. package/lib/orchestrator/index.js.map +1 -0
  430. package/lib/orchestrator/lifecycle-hook-execution.d.ts +4 -0
  431. package/lib/orchestrator/lifecycle-hook-execution.d.ts.map +1 -0
  432. package/lib/orchestrator/lifecycle-hook-execution.js +67 -0
  433. package/lib/orchestrator/lifecycle-hook-execution.js.map +1 -0
  434. package/lib/orchestrator/list-runs.d.ts +33 -0
  435. package/lib/orchestrator/list-runs.d.ts.map +1 -0
  436. package/lib/orchestrator/list-runs.js +308 -0
  437. package/lib/orchestrator/list-runs.js.map +1 -0
  438. package/lib/orchestrator/loader.d.ts +3 -0
  439. package/lib/orchestrator/loader.d.ts.map +1 -0
  440. package/lib/orchestrator/loader.js +17 -0
  441. package/lib/orchestrator/loader.js.map +1 -0
  442. package/lib/orchestrator/optimization-lifecycle.d.ts +12 -0
  443. package/lib/orchestrator/optimization-lifecycle.d.ts.map +1 -0
  444. package/lib/orchestrator/optimization-lifecycle.js +92 -0
  445. package/lib/orchestrator/optimization-lifecycle.js.map +1 -0
  446. package/lib/orchestrator/orchestrator.d.ts +144 -0
  447. package/lib/orchestrator/orchestrator.d.ts.map +1 -0
  448. package/lib/orchestrator/orchestrator.js +632 -0
  449. package/lib/orchestrator/orchestrator.js.map +1 -0
  450. package/lib/orchestrator/owned-lock.d.ts +17 -0
  451. package/lib/orchestrator/owned-lock.d.ts.map +1 -0
  452. package/lib/orchestrator/owned-lock.js +149 -0
  453. package/lib/orchestrator/owned-lock.js.map +1 -0
  454. package/lib/orchestrator/package-version.d.ts +2 -0
  455. package/lib/orchestrator/package-version.d.ts.map +1 -0
  456. package/lib/orchestrator/package-version.js +4 -0
  457. package/lib/orchestrator/package-version.js.map +1 -0
  458. package/lib/orchestrator/provenance.d.ts +5 -0
  459. package/lib/orchestrator/provenance.d.ts.map +1 -0
  460. package/lib/orchestrator/provenance.js +137 -0
  461. package/lib/orchestrator/provenance.js.map +1 -0
  462. package/lib/orchestrator/regrade.d.ts +80 -0
  463. package/lib/orchestrator/regrade.d.ts.map +1 -0
  464. package/lib/orchestrator/regrade.js +433 -0
  465. package/lib/orchestrator/regrade.js.map +1 -0
  466. package/lib/orchestrator/rubric-loader.d.ts +7 -0
  467. package/lib/orchestrator/rubric-loader.d.ts.map +1 -0
  468. package/lib/orchestrator/rubric-loader.js +65 -0
  469. package/lib/orchestrator/rubric-loader.js.map +1 -0
  470. package/lib/orchestrator/run-dir.d.ts +40 -0
  471. package/lib/orchestrator/run-dir.d.ts.map +1 -0
  472. package/lib/orchestrator/run-dir.js +161 -0
  473. package/lib/orchestrator/run-dir.js.map +1 -0
  474. package/lib/orchestrator/run-execution.d.ts +30 -0
  475. package/lib/orchestrator/run-execution.d.ts.map +1 -0
  476. package/lib/orchestrator/run-execution.js +531 -0
  477. package/lib/orchestrator/run-execution.js.map +1 -0
  478. package/lib/orchestrator/run-lifecycle.d.ts +115 -0
  479. package/lib/orchestrator/run-lifecycle.d.ts.map +1 -0
  480. package/lib/orchestrator/run-lifecycle.js +136 -0
  481. package/lib/orchestrator/run-lifecycle.js.map +1 -0
  482. package/lib/orchestrator/suite-compare.d.ts +76 -0
  483. package/lib/orchestrator/suite-compare.d.ts.map +1 -0
  484. package/lib/orchestrator/suite-compare.js +159 -0
  485. package/lib/orchestrator/suite-compare.js.map +1 -0
  486. package/lib/orchestrator/suite-lifecycle.d.ts +157 -0
  487. package/lib/orchestrator/suite-lifecycle.d.ts.map +1 -0
  488. package/lib/orchestrator/suite-lifecycle.js +253 -0
  489. package/lib/orchestrator/suite-lifecycle.js.map +1 -0
  490. package/lib/orchestrator/suite-runs.d.ts +91 -0
  491. package/lib/orchestrator/suite-runs.d.ts.map +1 -0
  492. package/lib/orchestrator/suite-runs.js +157 -0
  493. package/lib/orchestrator/suite-runs.js.map +1 -0
  494. package/lib/orchestrator/suite-summary.d.ts +109 -0
  495. package/lib/orchestrator/suite-summary.d.ts.map +1 -0
  496. package/lib/orchestrator/suite-summary.js +213 -0
  497. package/lib/orchestrator/suite-summary.js.map +1 -0
  498. package/lib/orchestrator/trace-limits.d.ts +20 -0
  499. package/lib/orchestrator/trace-limits.d.ts.map +1 -0
  500. package/lib/orchestrator/trace-limits.js +28 -0
  501. package/lib/orchestrator/trace-limits.js.map +1 -0
  502. package/lib/orchestrator/validate-plugin-config.d.ts +10 -0
  503. package/lib/orchestrator/validate-plugin-config.d.ts.map +1 -0
  504. package/lib/orchestrator/validate-plugin-config.js +26 -0
  505. package/lib/orchestrator/validate-plugin-config.js.map +1 -0
  506. package/package.json +84 -0
  507. package/skills/lux/SKILL.md +85 -0
  508. package/skills/lux/references/cli.md +102 -0
  509. package/skills/lux/references/eval-authoring.md +105 -0
  510. package/skills/lux-answerer/SKILL.md +169 -0
  511. package/src/answerers/claude-code/index.ts +342 -0
  512. package/src/answerers/persona/index.ts +327 -0
  513. package/src/answerers/scripted/index.ts +186 -0
  514. package/src/answerers/shared.ts +23 -0
  515. package/src/answerers/terminal/index.ts +117 -0
  516. package/src/assertions/core/command-exits-zero.ts +94 -0
  517. package/src/assertions/core/event-checks.ts +138 -0
  518. package/src/assertions/core/file-checks.ts +336 -0
  519. package/src/assertions/core/helpers.ts +203 -0
  520. package/src/assertions/core/index.ts +80 -0
  521. package/src/assertions/core/json-pointer-equals.ts +84 -0
  522. package/src/assertions/core/output-checks.ts +134 -0
  523. package/src/assertions/core/predicate.ts +67 -0
  524. package/src/assertions/core/resource-checks.ts +180 -0
  525. package/src/assertions/core/safe-regex.ts +196 -0
  526. package/src/assertions/core/tool-called-with.ts +68 -0
  527. package/src/assertions/core/tool-calls.ts +28 -0
  528. package/src/assertions/core/tool-invocation.ts +198 -0
  529. package/src/assertions/core/tool-result-checks.ts +211 -0
  530. package/src/assertions/mcp/discovery-checks.ts +139 -0
  531. package/src/assertions/mcp/index.ts +37 -0
  532. package/src/assertions/rubric/index.ts +258 -0
  533. package/src/assertions/rubric/internal.ts +367 -0
  534. package/src/authoring.ts +2 -0
  535. package/src/cli/bin.ts +14 -0
  536. package/src/cli/command-runtime.ts +34 -0
  537. package/src/cli/init.ts +410 -0
  538. package/src/cli/program.ts +992 -0
  539. package/src/cli/render/format.ts +69 -0
  540. package/src/cli/render/reporter.ts +643 -0
  541. package/src/cli/render/tty.ts +314 -0
  542. package/src/cli/run-options.ts +82 -0
  543. package/src/cli/skills.ts +129 -0
  544. package/src/core/annotation.ts +104 -0
  545. package/src/core/answerer.ts +75 -0
  546. package/src/core/artifact-evidence.ts +249 -0
  547. package/src/core/artifact-snapshot.ts +429 -0
  548. package/src/core/assertion.ts +156 -0
  549. package/src/core/case.ts +103 -0
  550. package/src/core/conversation-turn.ts +47 -0
  551. package/src/core/driver.ts +75 -0
  552. package/src/core/emitter.ts +38 -0
  553. package/src/core/events.ts +135 -0
  554. package/src/core/exec.ts +263 -0
  555. package/src/core/experiment.ts +467 -0
  556. package/src/core/glob.ts +31 -0
  557. package/src/core/hash.ts +237 -0
  558. package/src/core/index.ts +28 -0
  559. package/src/core/lifecycle-fixtures.ts +145 -0
  560. package/src/core/lifecycle-hooks.ts +101 -0
  561. package/src/core/mcp-events.ts +57 -0
  562. package/src/core/model-cli.ts +271 -0
  563. package/src/core/observer.ts +50 -0
  564. package/src/core/platform-process.ts +284 -0
  565. package/src/core/registry.ts +57 -0
  566. package/src/core/rubric.ts +52 -0
  567. package/src/core/run.ts +144 -0
  568. package/src/core/statistics.ts +194 -0
  569. package/src/core/subject.ts +50 -0
  570. package/src/core/tool-events.ts +292 -0
  571. package/src/core/usage.ts +123 -0
  572. package/src/core/wire.ts +135 -0
  573. package/src/core/zod.ts +20 -0
  574. package/src/drivers/claude-code/index.ts +866 -0
  575. package/src/drivers/claude-code/parse-events.ts +464 -0
  576. package/src/drivers/codex/app-events.ts +218 -0
  577. package/src/drivers/codex/config.ts +128 -0
  578. package/src/drivers/codex/index.ts +995 -0
  579. package/src/drivers/mcp/index.ts +351 -0
  580. package/src/drivers/mcp/transport.ts +222 -0
  581. package/src/drivers/shared/artifacts.ts +86 -0
  582. package/src/drivers/shared/secure-copy.ts +40 -0
  583. package/src/drivers/subprocess/index.ts +490 -0
  584. package/src/index.ts +594 -0
  585. package/src/optimization/reflective.ts +319 -0
  586. package/src/orchestrator/annotation-loader.ts +97 -0
  587. package/src/orchestrator/annotation-store.ts +112 -0
  588. package/src/orchestrator/authored-dependencies.ts +193 -0
  589. package/src/orchestrator/campaign-lifecycle.ts +181 -0
  590. package/src/orchestrator/candidate-integrity.ts +18 -0
  591. package/src/orchestrator/candidates.ts +683 -0
  592. package/src/orchestrator/compare.ts +358 -0
  593. package/src/orchestrator/comparison-identity.ts +45 -0
  594. package/src/orchestrator/config.ts +205 -0
  595. package/src/orchestrator/content-bound-json.ts +71 -0
  596. package/src/orchestrator/discover.ts +132 -0
  597. package/src/orchestrator/doctor.ts +781 -0
  598. package/src/orchestrator/driver-session.ts +507 -0
  599. package/src/orchestrator/evaluator-identity.ts +45 -0
  600. package/src/orchestrator/experiment-budget.ts +61 -0
  601. package/src/orchestrator/experiment-context.ts +41 -0
  602. package/src/orchestrator/experiment-contracts.ts +43 -0
  603. package/src/orchestrator/experiment-corpus.ts +416 -0
  604. package/src/orchestrator/experiment-evaluation.ts +749 -0
  605. package/src/orchestrator/experiment-finish.ts +33 -0
  606. package/src/orchestrator/experiment-identity.ts +356 -0
  607. package/src/orchestrator/experiment-loader.ts +105 -0
  608. package/src/orchestrator/experiment-report.ts +788 -0
  609. package/src/orchestrator/experiment-runs.ts +960 -0
  610. package/src/orchestrator/experiment-runtime-identity.ts +465 -0
  611. package/src/orchestrator/experiment-selection.ts +467 -0
  612. package/src/orchestrator/experiment.ts +991 -0
  613. package/src/orchestrator/fixture-identity.ts +67 -0
  614. package/src/orchestrator/fs-read.ts +126 -0
  615. package/src/orchestrator/grader-experiment.ts +856 -0
  616. package/src/orchestrator/grading.ts +260 -0
  617. package/src/orchestrator/index.ts +186 -0
  618. package/src/orchestrator/lifecycle-hook-execution.ts +90 -0
  619. package/src/orchestrator/list-runs.ts +384 -0
  620. package/src/orchestrator/loader.ts +19 -0
  621. package/src/orchestrator/optimization-lifecycle.ts +126 -0
  622. package/src/orchestrator/orchestrator.ts +919 -0
  623. package/src/orchestrator/owned-lock.ts +147 -0
  624. package/src/orchestrator/package-version.ts +5 -0
  625. package/src/orchestrator/provenance.ts +140 -0
  626. package/src/orchestrator/regrade.ts +580 -0
  627. package/src/orchestrator/rubric-loader.ts +74 -0
  628. package/src/orchestrator/run-dir.ts +209 -0
  629. package/src/orchestrator/run-execution.ts +694 -0
  630. package/src/orchestrator/run-lifecycle.ts +174 -0
  631. package/src/orchestrator/suite-compare.ts +195 -0
  632. package/src/orchestrator/suite-lifecycle.ts +335 -0
  633. package/src/orchestrator/suite-runs.ts +202 -0
  634. package/src/orchestrator/suite-summary.ts +276 -0
  635. package/src/orchestrator/trace-limits.ts +65 -0
  636. package/src/orchestrator/validate-plugin-config.ts +34 -0
@@ -0,0 +1,683 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { cp, lstat, mkdir, open, readdir, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { hostname } from "node:os";
4
+ import { basename, dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
5
+ import { temporaryDirectory } from "tempy";
6
+ import { glob } from "tinyglobby";
7
+ import { z } from "zod";
8
+ import { exec } from "../core/exec.js";
9
+ import {
10
+ type Candidate,
11
+ type CandidateProposal,
12
+ CandidateProposalSchema,
13
+ contentHash,
14
+ type FileSetSubject,
15
+ matchesAnyGlob,
16
+ stableJson,
17
+ valueHash,
18
+ } from "../core/index.js";
19
+ import { assertCandidateEnvelopeIntegrity } from "./candidate-integrity.js";
20
+ import { claimLockForRecovery, publishOwnedLock, releaseOwnedLock } from "./owned-lock.js";
21
+
22
+ const normalizedRelative = (path: string): string => {
23
+ if (isAbsolute(path)) throw new Error(`candidate component must be relative: ${path}`);
24
+ const normalized = normalize(path).replaceAll("\\", "/").replace(/^\.\//, "");
25
+ if (!normalized || normalized === ".." || normalized.startsWith("../")) {
26
+ throw new Error(`candidate component escapes the subject root: ${path}`);
27
+ }
28
+ return normalized;
29
+ };
30
+
31
+ const allowedComponent = (path: string, subject: FileSetSubject): string => {
32
+ const normalized = normalizedRelative(path);
33
+ if (!matchesAnyGlob(normalized, subject.mutable) || matchesAnyGlob(normalized, subject.exclude)) {
34
+ throw new Error(`candidate component is outside the mutable file set: ${normalized}`);
35
+ }
36
+ return normalized;
37
+ };
38
+
39
+ const readTextFile = async (path: string): Promise<string> => {
40
+ const bytes = await readFile(path);
41
+ if (bytes.includes(0)) throw new Error(`mutable candidate component is not text: ${path}`);
42
+ try {
43
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
44
+ } catch {
45
+ throw new Error(`mutable candidate component is not valid UTF-8: ${path}`);
46
+ }
47
+ };
48
+
49
+ export const hashSubjectTree = async (
50
+ subjectRoot: string,
51
+ subject: Pick<FileSetSubject, "exclude">,
52
+ ): Promise<string> => {
53
+ const entries: [path: string, type: "directory" | "file", mode: number, hash?: string][] = [];
54
+ const rootMetadata = await lstat(subjectRoot);
55
+ if (!rootMetadata.isDirectory() || rootMetadata.isSymbolicLink()) {
56
+ throw new Error("experiment subject root must be a real directory");
57
+ }
58
+ entries.push([".", "directory", rootMetadata.mode]);
59
+ const inspectDirectory = async (directory: string, prefix = ""): Promise<void> => {
60
+ const children = await readdir(directory, { withFileTypes: true });
61
+ for (const entry of children.sort((left, right) => left.name.localeCompare(right.name))) {
62
+ const path = prefix ? `${prefix}/${entry.name}` : entry.name;
63
+ if (
64
+ matchesAnyGlob(path, subject.exclude) ||
65
+ matchesAnyGlob(`${path}/__descendant__`, subject.exclude)
66
+ ) {
67
+ continue;
68
+ }
69
+ const fullPath = join(directory, entry.name);
70
+ const metadata = await lstat(fullPath);
71
+ if (metadata.isSymbolicLink()) {
72
+ throw new Error(`experiment subjects cannot contain symbolic links: ${path}`);
73
+ }
74
+ if (metadata.isDirectory()) {
75
+ entries.push([path, "directory", metadata.mode]);
76
+ await inspectDirectory(fullPath, path);
77
+ continue;
78
+ }
79
+ if (!metadata.isFile()) {
80
+ throw new Error(`experiment subjects cannot contain special filesystem entries: ${path}`);
81
+ }
82
+ entries.push([path, "file", metadata.mode, contentHash(await readFile(fullPath))]);
83
+ }
84
+ };
85
+ await inspectDirectory(subjectRoot);
86
+ return contentHash(stableJson(entries));
87
+ };
88
+
89
+ export const captureSeedCandidate = async (
90
+ subjectRoot: string,
91
+ subject: FileSetSubject,
92
+ now: Date = new Date(),
93
+ ): Promise<Candidate> => {
94
+ const files = await glob(subject.mutable, {
95
+ cwd: subjectRoot,
96
+ dot: true,
97
+ onlyFiles: true,
98
+ followSymbolicLinks: false,
99
+ ignore: subject.exclude,
100
+ });
101
+ if (files.length === 0) {
102
+ throw new Error(`subject mutable patterns matched no files under ${subjectRoot}`);
103
+ }
104
+ const components: Record<string, string> = Object.fromEntries(
105
+ await Promise.all(
106
+ files
107
+ .sort()
108
+ .map(
109
+ async (path) =>
110
+ [normalizedRelative(path), await readTextFile(join(subjectRoot, path))] as const,
111
+ ),
112
+ ),
113
+ );
114
+ return {
115
+ schemaVersion: 1,
116
+ id: "baseline",
117
+ parentIds: [],
118
+ createdAt: now.toISOString(),
119
+ proposer: "source",
120
+ baseSourceHash: await hashSubjectTree(subjectRoot, subject),
121
+ candidateHash: valueHash(components),
122
+ components,
123
+ sourceBytes: Object.values(components).reduce(
124
+ (total, content) => total + Buffer.byteLength(content),
125
+ 0,
126
+ ),
127
+ };
128
+ };
129
+
130
+ export const createCandidate = (
131
+ parent: Candidate,
132
+ proposal: CandidateProposal,
133
+ subject: FileSetSubject,
134
+ options: { id?: string; proposer: string; now?: Date },
135
+ ): Candidate => {
136
+ const parsedProposal = CandidateProposalSchema.parse(proposal);
137
+ const updates = Object.fromEntries(
138
+ Object.entries(parsedProposal.updates).map(([path, content]) => {
139
+ const normalized = allowedComponent(path, subject);
140
+ if (!Object.hasOwn(parent.components, normalized)) {
141
+ throw new Error(`candidate proposal cannot add a new component: ${normalized}`);
142
+ }
143
+ if (content.includes("\0")) {
144
+ throw new Error(`candidate proposal contains NUL bytes: ${normalized}`);
145
+ }
146
+ return [normalized, content];
147
+ }),
148
+ );
149
+ if (Object.keys(updates).length === 0) {
150
+ throw new Error("candidate proposal contains no component updates");
151
+ }
152
+ const components = { ...parent.components, ...updates };
153
+ const candidateHash = valueHash(components);
154
+ return {
155
+ schemaVersion: 1,
156
+ id: options.id ?? `candidate-${candidateHash.slice(0, 12)}`,
157
+ parentIds: [parent.id],
158
+ createdAt: (options.now ?? new Date()).toISOString(),
159
+ proposer: options.proposer,
160
+ baseSourceHash: parent.baseSourceHash,
161
+ candidateHash,
162
+ components,
163
+ sourceBytes: Object.values(components).reduce(
164
+ (total, content) => total + Buffer.byteLength(content),
165
+ 0,
166
+ ),
167
+ ...(parsedProposal.rationale ? { rationale: parsedProposal.rationale } : {}),
168
+ ...(parsedProposal.usage ? { proposalUsage: parsedProposal.usage } : {}),
169
+ };
170
+ };
171
+
172
+ const excludedFromCopy = (root: string, path: string, excludes: readonly string[]): boolean => {
173
+ const rel = relative(root, path).split(sep).join("/");
174
+ if (!rel) return false;
175
+ return matchesAnyGlob(rel, excludes);
176
+ };
177
+
178
+ export const materializeCandidate = async (
179
+ sourceRoot: string,
180
+ subject: FileSetSubject,
181
+ candidate: Candidate,
182
+ ): Promise<string> => {
183
+ const verified = assertCandidateEnvelopeIntegrity(candidate);
184
+ const currentHash = await hashSubjectTree(sourceRoot, subject);
185
+ if (currentHash !== verified.baseSourceHash) {
186
+ throw new Error("experiment subject source changed before candidate materialization");
187
+ }
188
+ const target = temporaryDirectory({ prefix: `lux-candidate-${verified.id}-` });
189
+ try {
190
+ await cp(sourceRoot, target, {
191
+ recursive: true,
192
+ filter: (source) => !excludedFromCopy(sourceRoot, source, subject.exclude),
193
+ });
194
+ for (const [path, content] of Object.entries(verified.components)) {
195
+ const relativePath = allowedComponent(path, subject);
196
+ const destination = resolve(target, relativePath);
197
+ await mkdir(resolve(destination, ".."), { recursive: true });
198
+ await writeFile(destination, content);
199
+ }
200
+ return target;
201
+ } catch (error) {
202
+ await rm(target, { recursive: true, force: true });
203
+ throw error;
204
+ }
205
+ };
206
+
207
+ export const validateCandidate = async (
208
+ materializedRoot: string,
209
+ subject: FileSetSubject,
210
+ signal?: AbortSignal,
211
+ verifyExecutionIdentity?: () => Promise<void>,
212
+ ): Promise<void> => {
213
+ const expectedTreeHash = await hashSubjectTree(materializedRoot, { exclude: [] });
214
+ for (const validation of subject.validate) {
215
+ assertValidationArgumentsBound(materializedRoot, validation.args);
216
+ const validationHome = temporaryDirectory({ prefix: "lux-validation-home-" });
217
+ const env = Object.fromEntries(
218
+ ["PATH", "LANG", "LC_ALL", "TERM", "TMPDIR", "CI", "NO_COLOR"]
219
+ .flatMap((name) =>
220
+ process.env[name] === undefined ? [] : [[name, process.env[name] as string]],
221
+ )
222
+ .concat([["HOME", validationHome]]),
223
+ );
224
+ const result = await (async () => {
225
+ await verifyExecutionIdentity?.();
226
+ try {
227
+ return await exec(validation.command, validation.args, {
228
+ cwd: materializedRoot,
229
+ timeoutMs: validation.timeoutMs,
230
+ env,
231
+ ...(signal ? { signal } : {}),
232
+ });
233
+ } finally {
234
+ await verifyExecutionIdentity?.();
235
+ }
236
+ })().finally(() => rm(validationHome, { recursive: true, force: true }));
237
+ if (result.timedOut || result.exitCode !== 0) {
238
+ const reason = result.timedOut
239
+ ? `timed out after ${validation.timeoutMs}ms`
240
+ : `exited ${result.exitCode}`;
241
+ throw new Error(
242
+ `candidate validation '${validation.command}' ${reason}: ${result.stderr.slice(0, 500)}`,
243
+ );
244
+ }
245
+ let currentTreeHash: string;
246
+ try {
247
+ currentTreeHash = await hashSubjectTree(materializedRoot, { exclude: [] });
248
+ } catch (error) {
249
+ const reason = error instanceof Error ? error.message : String(error);
250
+ throw new Error(
251
+ `candidate validation '${validation.command}' modified the materialized candidate; ` +
252
+ `validators must be observational/read-only: ${reason}`,
253
+ );
254
+ }
255
+ if (currentTreeHash !== expectedTreeHash) {
256
+ throw new Error(
257
+ `candidate validation '${validation.command}' modified the materialized candidate; ` +
258
+ "validators must be observational/read-only",
259
+ );
260
+ }
261
+ }
262
+ };
263
+
264
+ const validationArgumentValues = (argument: string): string[] => {
265
+ const values = [argument];
266
+ const equals = argument.indexOf("=");
267
+ if (equals >= 0) values.push(argument.slice(equals + 1));
268
+ const longOption = /^--[^:=]+:(.+)$/.exec(argument)?.[1];
269
+ if (longOption) values.push(longOption);
270
+ const shortOption = /^-[A-Za-z](.+)$/.exec(argument)?.[1];
271
+ if (shortOption) values.push(shortOption);
272
+ if (argument.startsWith("@") && argument.length > 1) values.push(argument.slice(1));
273
+ return [...new Set(values)];
274
+ };
275
+
276
+ export const assertValidationArgumentsBound = (
277
+ subjectRoot: string,
278
+ args: readonly string[],
279
+ ): void => {
280
+ for (const argument of args) {
281
+ if (/(?:file:|[a-z][a-z0-9+.-]*:\/\/)/i.test(argument)) {
282
+ throw new Error(`candidate validation argument references external behavior: ${argument}`);
283
+ }
284
+ if (argument.startsWith("-") && /[\\/]/.test(argument)) {
285
+ throw new Error(
286
+ `candidate validation path values must be standalone relative arguments: ${argument}`,
287
+ );
288
+ }
289
+ for (const value of validationArgumentValues(argument)) {
290
+ if (!value || value.startsWith("-")) continue;
291
+ if (
292
+ isAbsolute(value) ||
293
+ /^[A-Za-z]:[\\/]/.test(value) ||
294
+ /^\\\\/.test(value) ||
295
+ /^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)
296
+ ) {
297
+ throw new Error(`candidate validation argument references external behavior: ${argument}`);
298
+ }
299
+ const path = resolve(subjectRoot, value);
300
+ const fromSubject = relative(resolve(subjectRoot), path);
301
+ if (fromSubject === ".." || fromSubject.startsWith(`..${sep}`) || isAbsolute(fromSubject)) {
302
+ throw new Error(
303
+ `candidate validation argument escapes the identity-bound subject: ${argument}`,
304
+ );
305
+ }
306
+ }
307
+ }
308
+ };
309
+
310
+ const ApplyJournalEntrySchema = z
311
+ .object({
312
+ destination: z.string(),
313
+ temporary: z.string(),
314
+ backup: z.string(),
315
+ mode: z.number().int().nonnegative(),
316
+ originalHash: z.string(),
317
+ })
318
+ .strict();
319
+
320
+ const ApplyJournalSchema = z
321
+ .object({
322
+ schemaVersion: z.literal(1),
323
+ subjectRoot: z.string(),
324
+ candidateHash: z.string(),
325
+ status: z.enum(["staging", "committing", "committed"]),
326
+ entries: z.array(ApplyJournalEntrySchema),
327
+ })
328
+ .strict();
329
+ type ApplyJournal = z.infer<typeof ApplyJournalSchema>;
330
+ type ApplyJournalEntry = z.infer<typeof ApplyJournalEntrySchema>;
331
+
332
+ const ApplyLockSchema = z
333
+ .object({
334
+ schemaVersion: z.literal(1),
335
+ pid: z.number().int().positive(),
336
+ hostname: z.string(),
337
+ acquiredAt: z.string(),
338
+ subjectRoot: z.string(),
339
+ candidateHash: z.string(),
340
+ stageRoot: z.string(),
341
+ lockToken: z.exactOptional(z.string()),
342
+ status: z.enum(["running", "committed"]).default("running"),
343
+ })
344
+ .strict();
345
+ type ApplyLock = z.infer<typeof ApplyLockSchema>;
346
+ type OwnedApplyLock = ApplyLock & { lockToken: string };
347
+
348
+ const applyPaths = (subjectRoot: string): { root: string; parent: string; lockPath: string } => {
349
+ const root = resolve(subjectRoot);
350
+ const parent = dirname(root);
351
+ return {
352
+ root,
353
+ parent,
354
+ lockPath: join(parent, `.lux-apply-${basename(root)}.lock`),
355
+ };
356
+ };
357
+
358
+ const syncDirectory = async (path: string): Promise<void> => {
359
+ let handle: Awaited<ReturnType<typeof open>> | undefined;
360
+ try {
361
+ handle = await open(path, "r");
362
+ await handle.sync();
363
+ } catch (error) {
364
+ const code = (error as NodeJS.ErrnoException).code;
365
+ if (process.platform === "win32" && code === "EPERM") return;
366
+ if (code !== "EINVAL" && code !== "ENOTSUP" && code !== "EISDIR") throw error;
367
+ } finally {
368
+ await handle?.close();
369
+ }
370
+ };
371
+
372
+ const writeDurableExclusive = async (
373
+ path: string,
374
+ contents: string | Uint8Array,
375
+ mode?: number,
376
+ ): Promise<void> => {
377
+ const handle = await open(path, "wx", mode);
378
+ try {
379
+ await handle.writeFile(contents);
380
+ if (mode !== undefined) await handle.chmod(mode);
381
+ await handle.sync();
382
+ } finally {
383
+ await handle.close();
384
+ }
385
+ };
386
+
387
+ const writeJsonDurableAtomic = async (path: string, value: unknown): Promise<void> => {
388
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
389
+ await writeDurableExclusive(temporary, `${JSON.stringify(value, null, 2)}\n`);
390
+ try {
391
+ await rename(temporary, path);
392
+ await syncDirectory(dirname(path));
393
+ } finally {
394
+ await rm(temporary, { force: true });
395
+ }
396
+ };
397
+
398
+ const assertRecoveryPath = (
399
+ path: string,
400
+ parent: string,
401
+ expectedPrefix: string,
402
+ description: string,
403
+ ): void => {
404
+ const fromParent = relative(parent, path);
405
+ if (
406
+ fromParent === "" ||
407
+ fromParent === ".." ||
408
+ fromParent.startsWith(`..${sep}`) ||
409
+ isAbsolute(fromParent) ||
410
+ dirname(fromParent) !== "." ||
411
+ !basename(fromParent).startsWith(expectedPrefix)
412
+ ) {
413
+ throw new Error(`interrupted candidate ${description} is outside its expected location`);
414
+ }
415
+ };
416
+
417
+ const assertJournalContained = (
418
+ journal: ApplyJournal,
419
+ expectedRoot: string,
420
+ stageRoot: string,
421
+ ): void => {
422
+ if (resolve(journal.subjectRoot) !== expectedRoot) {
423
+ throw new Error("interrupted candidate journal targets a different subject root");
424
+ }
425
+ for (const entry of journal.entries) {
426
+ const fromRoot = relative(expectedRoot, resolve(entry.destination));
427
+ if (
428
+ fromRoot === "" ||
429
+ fromRoot === ".." ||
430
+ fromRoot.startsWith(`..${sep}`) ||
431
+ isAbsolute(fromRoot)
432
+ ) {
433
+ throw new Error("interrupted candidate destination escapes the subject root");
434
+ }
435
+ for (const [kind, path] of [
436
+ ["backup", entry.backup],
437
+ ["temporary", entry.temporary],
438
+ ] as const) {
439
+ const fromStage = relative(stageRoot, resolve(path));
440
+ if (
441
+ fromStage === "" ||
442
+ fromStage === ".." ||
443
+ fromStage.startsWith(`..${sep}`) ||
444
+ isAbsolute(fromStage) ||
445
+ dirname(fromStage) !== "."
446
+ ) {
447
+ throw new Error(`interrupted candidate ${kind} escapes its staging location`);
448
+ }
449
+ }
450
+ }
451
+ };
452
+
453
+ const restoreJournalEntry = async (entry: ApplyJournalEntry): Promise<void> => {
454
+ const backup = await readFile(entry.backup);
455
+ if (contentHash(backup) !== entry.originalHash) {
456
+ throw new Error(`candidate rollback backup failed its integrity check: ${entry.backup}`);
457
+ }
458
+ const temporary = join(
459
+ dirname(entry.destination),
460
+ `.lux-apply-restore-${basename(entry.destination)}-${randomUUID()}.tmp`,
461
+ );
462
+ await writeDurableExclusive(temporary, backup, entry.mode);
463
+ try {
464
+ await rename(temporary, entry.destination);
465
+ await syncDirectory(dirname(entry.destination));
466
+ } finally {
467
+ await rm(temporary, { force: true });
468
+ }
469
+ };
470
+
471
+ const ownerIsAlive = (owner: ApplyLock): boolean => {
472
+ if (owner.hostname !== hostname()) return true;
473
+ try {
474
+ process.kill(owner.pid, 0);
475
+ return true;
476
+ } catch (error) {
477
+ const code = (error as NodeJS.ErrnoException).code;
478
+ if (code === "ESRCH") return false;
479
+ if (code === "EPERM") return true;
480
+ throw error;
481
+ }
482
+ };
483
+
484
+ const recoverInterruptedApply = async (
485
+ lockPath: string,
486
+ expectedRoot: string,
487
+ ): Promise<{ committedCandidateHash?: string }> => {
488
+ const claim = await claimLockForRecovery(lockPath);
489
+ if (!claim) return {};
490
+ let owner: ApplyLock;
491
+ try {
492
+ try {
493
+ owner = ApplyLockSchema.parse(JSON.parse(await readFile(claim.path, "utf8")));
494
+ } catch {
495
+ throw new Error(`candidate application lock is present but unreadable: ${lockPath}`);
496
+ }
497
+ if (resolve(owner.subjectRoot) !== expectedRoot) {
498
+ throw new Error("candidate application lock targets a different subject root");
499
+ }
500
+ if (ownerIsAlive(owner)) {
501
+ throw new Error(`candidate application is already owned by process ${owner.pid}`);
502
+ }
503
+ const { parent } = applyPaths(expectedRoot);
504
+ const stageRoot = resolve(owner.stageRoot);
505
+ assertRecoveryPath(stageRoot, parent, `.lux-apply-stage-${basename(expectedRoot)}-`, "stage");
506
+ let journal: ApplyJournal | undefined;
507
+ try {
508
+ journal = ApplyJournalSchema.parse(
509
+ JSON.parse(await readFile(join(stageRoot, "transaction.json"), "utf8")),
510
+ );
511
+ } catch (error) {
512
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
513
+ throw new Error(`candidate application journal is unreadable: ${stageRoot}`);
514
+ }
515
+ }
516
+ if (journal) {
517
+ assertJournalContained(journal, expectedRoot, stageRoot);
518
+ if (journal.candidateHash !== owner.candidateHash) {
519
+ throw new Error("candidate application lock and journal disagree");
520
+ }
521
+ if (owner.status === "committed" && journal.status !== "committed") {
522
+ throw new Error("candidate application lock claims an incomplete journal is committed");
523
+ }
524
+ if (journal.status === "committing") {
525
+ for (const entry of [...journal.entries].reverse()) await restoreJournalEntry(entry);
526
+ }
527
+ }
528
+ await rm(stageRoot, { recursive: true, force: true });
529
+ await claim.removeLock();
530
+ await syncDirectory(parent);
531
+ return journal?.status === "committed" || owner.status === "committed"
532
+ ? { committedCandidateHash: owner.candidateHash }
533
+ : {};
534
+ } finally {
535
+ await claim.release();
536
+ }
537
+ };
538
+
539
+ const acquireApply = async (
540
+ subjectRoot: string,
541
+ candidateHash: string,
542
+ ): Promise<
543
+ | { alreadyCommitted: true }
544
+ | { alreadyCommitted: false; lockPath: string; stageRoot: string; owner: OwnedApplyLock }
545
+ > => {
546
+ const { root, parent, lockPath } = applyPaths(subjectRoot);
547
+ const stageRoot = join(
548
+ parent,
549
+ `.lux-apply-stage-${basename(root)}-${process.pid}-${randomUUID()}`,
550
+ );
551
+ let owner: OwnedApplyLock;
552
+ try {
553
+ owner = await publishOwnedLock(lockPath, {
554
+ schemaVersion: 1,
555
+ pid: process.pid,
556
+ hostname: hostname(),
557
+ acquiredAt: new Date().toISOString(),
558
+ subjectRoot: root,
559
+ candidateHash,
560
+ stageRoot,
561
+ status: "running",
562
+ });
563
+ await syncDirectory(parent);
564
+ } catch (error) {
565
+ if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
566
+ const recovered = await recoverInterruptedApply(lockPath, root);
567
+ if (recovered.committedCandidateHash === candidateHash) {
568
+ return { alreadyCommitted: true };
569
+ }
570
+ return acquireApply(subjectRoot, candidateHash);
571
+ }
572
+ try {
573
+ await mkdir(stageRoot);
574
+ return { alreadyCommitted: false, lockPath, stageRoot, owner };
575
+ } catch (error) {
576
+ await releaseOwnedLock(lockPath, owner.lockToken);
577
+ throw error;
578
+ }
579
+ };
580
+
581
+ export const applyCandidate = async (
582
+ subjectRoot: string,
583
+ subject: FileSetSubject,
584
+ candidate: Candidate,
585
+ ): Promise<void> => {
586
+ assertCandidateEnvelopeIntegrity(candidate);
587
+ const rootMetadata = await lstat(resolve(subjectRoot));
588
+ if (!rootMetadata.isDirectory() || rootMetadata.isSymbolicLink()) {
589
+ throw new Error("candidate subject root must be a real directory");
590
+ }
591
+ const ownership = await acquireApply(subjectRoot, candidate.candidateHash);
592
+ if (ownership.alreadyCommitted) {
593
+ for (const [path, content] of Object.entries(candidate.components)) {
594
+ const destination = resolve(subjectRoot, allowedComponent(path, subject));
595
+ if ((await readTextFile(destination)) !== content) {
596
+ throw new Error(
597
+ "recovered candidate transaction was marked committed but its files do not match",
598
+ );
599
+ }
600
+ }
601
+ return;
602
+ }
603
+ const { lockPath, stageRoot, owner } = ownership;
604
+ const journalPath = join(stageRoot, "transaction.json");
605
+ let journal: ApplyJournal | undefined;
606
+ let preserveRecovery = false;
607
+ try {
608
+ const currentHash = await hashSubjectTree(subjectRoot, subject);
609
+ if (currentHash !== candidate.baseSourceHash) {
610
+ throw new Error(
611
+ `subject source changed since candidate creation (${candidate.baseSourceHash} != ${currentHash})`,
612
+ );
613
+ }
614
+ const entries: ApplyJournalEntry[] = [];
615
+ for (const [path, content] of Object.entries(candidate.components)) {
616
+ const destination = resolve(subjectRoot, allowedComponent(path, subject));
617
+ const metadata = await lstat(destination);
618
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
619
+ throw new Error(`candidate destination must be a regular file: ${path}`);
620
+ }
621
+ const index = entries.length;
622
+ const backup = join(stageRoot, `${index}.backup`);
623
+ const temporary = join(stageRoot, `${index}.next`);
624
+ const original = await readFile(destination);
625
+ await writeDurableExclusive(backup, original, metadata.mode);
626
+ await writeDurableExclusive(temporary, content, metadata.mode);
627
+ entries.push({
628
+ destination,
629
+ temporary,
630
+ backup,
631
+ mode: metadata.mode,
632
+ originalHash: contentHash(original),
633
+ });
634
+ }
635
+ journal = {
636
+ schemaVersion: 1,
637
+ subjectRoot: resolve(subjectRoot),
638
+ candidateHash: candidate.candidateHash,
639
+ status: "staging",
640
+ entries,
641
+ };
642
+ await writeJsonDurableAtomic(journalPath, journal);
643
+ const beforeCommitHash = await hashSubjectTree(subjectRoot, subject);
644
+ if (beforeCommitHash !== candidate.baseSourceHash) {
645
+ throw new Error("subject source changed while candidate application was being staged");
646
+ }
647
+ journal.status = "committing";
648
+ await writeJsonDurableAtomic(journalPath, journal);
649
+ for (const entry of journal.entries) {
650
+ await rename(entry.temporary, entry.destination);
651
+ await syncDirectory(dirname(entry.destination));
652
+ }
653
+ journal.status = "committed";
654
+ await writeJsonDurableAtomic(journalPath, journal);
655
+ owner.status = "committed";
656
+ await writeJsonDurableAtomic(lockPath, owner);
657
+ } catch (error) {
658
+ const rollbackErrors: unknown[] = [];
659
+ if (journal?.status === "committing") {
660
+ for (const entry of [...journal.entries].reverse()) {
661
+ try {
662
+ await restoreJournalEntry(entry);
663
+ } catch (rollbackError) {
664
+ rollbackErrors.push(rollbackError);
665
+ }
666
+ }
667
+ }
668
+ if (rollbackErrors.length > 0) {
669
+ preserveRecovery = true;
670
+ throw new AggregateError(
671
+ [error, ...rollbackErrors],
672
+ `candidate application failed and rollback was incomplete; recoverable backups remain at ${stageRoot}`,
673
+ );
674
+ }
675
+ throw error;
676
+ } finally {
677
+ if (!preserveRecovery) {
678
+ await rm(stageRoot, { recursive: true, force: true });
679
+ await releaseOwnedLock(lockPath, owner.lockToken);
680
+ await syncDirectory(dirname(lockPath));
681
+ }
682
+ }
683
+ };