@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,249 @@
1
+ import type { Stats } from "node:fs";
2
+ import { constants, lstat, open } from "node:fs/promises";
3
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import type { Artifact } from "./driver.js";
5
+ import type { Run } from "./run.js";
6
+
7
+ const DEFAULT_ARTIFACT_READ_BYTES = 5 * 1024 * 1024;
8
+ const MAX_ARTIFACT_READ_BYTES = 10 * 1024 * 1024;
9
+
10
+ export type RunArtifactInspection = {
11
+ target: string;
12
+ root: string;
13
+ artifact?: Artifact;
14
+ stats?: Stats;
15
+ issue?: string;
16
+ };
17
+
18
+ type RunArtifactEvidence = {
19
+ runDir: Run["runDir"];
20
+ artifacts: readonly Artifact[];
21
+ };
22
+
23
+ type ArtifactEvidenceBoundary = {
24
+ root: string;
25
+ indexedOnly: boolean;
26
+ };
27
+
28
+ const artifactEvidenceBoundaries = new WeakMap<object, ArtifactEvidenceBoundary>();
29
+
30
+ /** Internal: bind a frozen Run object to its isolated artifact snapshot. */
31
+ export const bindRunArtifactEvidence = (
32
+ run: RunArtifactEvidence,
33
+ boundary: ArtifactEvidenceBoundary,
34
+ ): void => {
35
+ artifactEvidenceBoundaries.set(run, { ...boundary, root: resolve(boundary.root) });
36
+ };
37
+
38
+ /** Internal: whether a Run is confined to an isolated evidence snapshot. */
39
+ export const hasRunArtifactEvidenceBoundary = (run: RunArtifactEvidence): boolean =>
40
+ artifactEvidenceBoundaries.has(run);
41
+
42
+ const pathEscapesRoot = (root: string, target: string): boolean => {
43
+ const path = relative(root, target);
44
+ return path === ".." || path.startsWith(`..${sep}`) || isAbsolute(path);
45
+ };
46
+
47
+ const defaultArtifactsRoot = (run: RunArtifactEvidence): string =>
48
+ artifactEvidenceBoundaries.get(run)?.root ?? resolve(run.runDir, "artifacts");
49
+
50
+ const rootsForArtifacts = (run: RunArtifactEvidence, artifacts: readonly Artifact[]): string[] => [
51
+ ...new Set(
52
+ artifacts.map((artifact) =>
53
+ artifact.root && isAbsolute(artifact.root)
54
+ ? resolve(artifact.root)
55
+ : defaultArtifactsRoot(run),
56
+ ),
57
+ ),
58
+ ];
59
+
60
+ /**
61
+ * Resolve the common root captured by a driver. The default run artifacts
62
+ * directory remains the fallback for empty or genuinely multi-root evidence.
63
+ */
64
+ export const runArtifactsRoot = (run: RunArtifactEvidence): string => {
65
+ const boundary = artifactEvidenceBoundaries.get(run);
66
+ if (boundary) return boundary.root;
67
+ const roots = rootsForArtifacts(run, run.artifacts);
68
+ return roots.length === 1 ? (roots[0] ?? defaultArtifactsRoot(run)) : defaultArtifactsRoot(run);
69
+ };
70
+
71
+ /**
72
+ * Inspect one captured artifact without following symlinks or allowing a path
73
+ * to escape the root recorded by its driver. An exact artifact-index entry
74
+ * wins; otherwise Lux uses the run's common captured-artifact root.
75
+ */
76
+ export const inspectRunArtifact = async (
77
+ run: RunArtifactEvidence,
78
+ authoredPath: string,
79
+ ): Promise<RunArtifactInspection> => {
80
+ if (authoredPath.includes("\0")) {
81
+ return {
82
+ target: authoredPath,
83
+ root: runArtifactsRoot(run),
84
+ issue: `${authoredPath} contains a null byte`,
85
+ };
86
+ }
87
+ const matching = run.artifacts.filter((candidate) => candidate.path === authoredPath);
88
+ if (artifactEvidenceBoundaries.get(run)?.indexedOnly && matching.length === 0) {
89
+ const root = defaultArtifactsRoot(run);
90
+ return {
91
+ target: resolve(root, authoredPath),
92
+ root,
93
+ issue: `${authoredPath} was not captured in the frozen artifact index`,
94
+ };
95
+ }
96
+ if (matching.some((candidate) => candidate.root !== undefined && !isAbsolute(candidate.root))) {
97
+ return {
98
+ target: authoredPath,
99
+ root: defaultArtifactsRoot(run),
100
+ issue: `${authoredPath} declares a non-absolute artifact root`,
101
+ };
102
+ }
103
+ const artifact = matching[0];
104
+ const identified = artifact ? { artifact } : {};
105
+ const matchingRoots = rootsForArtifacts(run, matching);
106
+ if (matchingRoots.length > 1) {
107
+ return {
108
+ target: authoredPath,
109
+ root: defaultArtifactsRoot(run),
110
+ issue: `${authoredPath} is ambiguous across multiple artifact roots`,
111
+ };
112
+ }
113
+ // A non-default root is trusted only for an exact artifact-index entry.
114
+ // Otherwise one captured path could turn its whole external root into
115
+ // assertion-readable evidence.
116
+ const root = matchingRoots[0] ?? defaultArtifactsRoot(run);
117
+ const target = resolve(root, authoredPath);
118
+ if (pathEscapesRoot(root, target)) {
119
+ return { target, root, issue: `${authoredPath} escapes the captured artifact root` };
120
+ }
121
+
122
+ const path = relative(root, target);
123
+ const components = [
124
+ root,
125
+ ...path
126
+ .split(sep)
127
+ .filter(Boolean)
128
+ .map((_, index, parts) => join(root, ...parts.slice(0, index + 1))),
129
+ ];
130
+ for (const [index, component] of components.entries()) {
131
+ let stats: Stats;
132
+ try {
133
+ stats = await lstat(component);
134
+ } catch (error) {
135
+ const code = (error as NodeJS.ErrnoException).code;
136
+ if (code === "ENOENT" || code === "ENOTDIR") {
137
+ return { target, root, ...identified, issue: `${authoredPath} not found` };
138
+ }
139
+ throw error;
140
+ }
141
+ if (stats.isSymbolicLink()) {
142
+ return {
143
+ target,
144
+ root,
145
+ ...identified,
146
+ issue: `${authoredPath} resolves through a symlink`,
147
+ };
148
+ }
149
+ if (index < components.length - 1 && !stats.isDirectory()) {
150
+ return { target, root, ...identified, issue: `${authoredPath} not found` };
151
+ }
152
+ if (index === components.length - 1) {
153
+ return { target, root, ...identified, stats };
154
+ }
155
+ }
156
+ return { target, root, ...identified, issue: `${authoredPath} not found` };
157
+ };
158
+
159
+ export type ReadRunArtifactTextOptions = {
160
+ /**
161
+ * Maximum bytes read before UTF-8 decoding. Defaults to 5 MiB and cannot
162
+ * exceed Lux's 10 MiB public artifact-read ceiling.
163
+ */
164
+ maxBytes?: number;
165
+ };
166
+
167
+ export const readInspectedFileBytes = async (
168
+ target: string,
169
+ expected: Stats,
170
+ maxBytes: number,
171
+ label: string,
172
+ ): Promise<Buffer> => {
173
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0 || maxBytes > MAX_ARTIFACT_READ_BYTES) {
174
+ throw new RangeError(
175
+ `artifact maxBytes must be an integer from 0 to ${MAX_ARTIFACT_READ_BYTES}`,
176
+ );
177
+ }
178
+ if (!expected.isFile()) throw new Error(`${label} is not a file`);
179
+ if (expected.size > maxBytes) {
180
+ throw new Error(`${label} exceeds the ${maxBytes}-byte artifact read limit`);
181
+ }
182
+
183
+ try {
184
+ const handle = await open(target, constants.O_RDONLY | constants.O_NOFOLLOW);
185
+ try {
186
+ const opened = await handle.stat();
187
+ if (!opened.isFile()) throw new Error(`${label} is not a file`);
188
+ if (opened.dev !== expected.dev || opened.ino !== expected.ino) {
189
+ throw new Error(`${label} changed while it was being inspected`);
190
+ }
191
+ if (opened.size !== expected.size) {
192
+ throw new Error(`${label} changed while it was being inspected`);
193
+ }
194
+ if (opened.size > maxBytes) {
195
+ throw new Error(`${label} exceeds the ${maxBytes}-byte artifact read limit`);
196
+ }
197
+ // Allocate for the inspected file, not the caller's ceiling. Reading one
198
+ // extra byte detects same-inode growth after fstat without making many
199
+ // tiny artifact reads reserve the maximum concurrently.
200
+ const bytes = Buffer.alloc(expected.size + 1);
201
+ let offset = 0;
202
+ while (offset < bytes.length) {
203
+ const read = await handle.read(bytes, offset, bytes.length - offset, offset);
204
+ if (read.bytesRead === 0) break;
205
+ offset += read.bytesRead;
206
+ }
207
+ if (offset !== expected.size) {
208
+ throw new Error(`${label} changed while it was being inspected`);
209
+ }
210
+ return bytes.subarray(0, offset);
211
+ } finally {
212
+ await handle.close();
213
+ }
214
+ } catch (error) {
215
+ if ((error as NodeJS.ErrnoException).code === "ELOOP") {
216
+ throw new Error(`${label} resolves through a symlink`);
217
+ }
218
+ throw error;
219
+ }
220
+ };
221
+
222
+ export const readRunArtifactBytes = async (
223
+ run: RunArtifactEvidence,
224
+ authoredPath: string,
225
+ options: ReadRunArtifactTextOptions = {},
226
+ ): Promise<Buffer> => {
227
+ const maxBytes = options.maxBytes ?? DEFAULT_ARTIFACT_READ_BYTES;
228
+ const inspected = await inspectRunArtifact(run, authoredPath);
229
+ if (!inspected.stats?.isFile()) {
230
+ throw new Error(
231
+ inspected.issue ??
232
+ (inspected.stats ? `${authoredPath} is not a file` : `${authoredPath} not found`),
233
+ );
234
+ }
235
+ return await readInspectedFileBytes(inspected.target, inspected.stats, maxBytes, authoredPath);
236
+ };
237
+
238
+ /**
239
+ * Read UTF-8 artifact evidence through Lux's traversal, symlink, regular-file,
240
+ * and size checks. Throws a bounded, path-specific error when evidence is not
241
+ * safe to consume.
242
+ */
243
+ export const readRunArtifactText = async (
244
+ run: RunArtifactEvidence,
245
+ authoredPath: string,
246
+ options: ReadRunArtifactTextOptions = {},
247
+ ): Promise<string> => {
248
+ return (await readRunArtifactBytes(run, authoredPath, options)).toString("utf8");
249
+ };
@@ -0,0 +1,429 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ chmod,
4
+ constants,
5
+ copyFile,
6
+ lstat,
7
+ mkdir,
8
+ mkdtemp,
9
+ open,
10
+ readFile,
11
+ realpath,
12
+ rename,
13
+ rm,
14
+ unlink,
15
+ } from "node:fs/promises";
16
+ import { dirname, join, posix, relative, resolve, sep } from "node:path";
17
+ import { z } from "zod";
18
+
19
+ const DEFAULT_MAX_FILES = 100_000;
20
+ const DEFAULT_MAX_BYTES = 256 * 1024 * 1024;
21
+ const DEFAULT_WRITE_CONCURRENCY = 16;
22
+ const MAX_MANIFEST_BYTES = 64 * 1024 * 1024;
23
+ const MAX_SNAPSHOT_PATH_BYTES = 4_096;
24
+ export const ARTIFACT_SNAPSHOT_MANIFEST = ".lux-snapshot.json";
25
+ const SNAPSHOT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
26
+
27
+ export const ArtifactSnapshotFileSchema = z.object({
28
+ path: z
29
+ .string()
30
+ .min(1)
31
+ .refine((path) => {
32
+ try {
33
+ return snapshotPath(path) === path;
34
+ } catch {
35
+ return false;
36
+ }
37
+ }, "invalid snapshot path"),
38
+ bytes: z.number().int().nonnegative(),
39
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
40
+ });
41
+ export type ArtifactSnapshotFile = z.infer<typeof ArtifactSnapshotFileSchema>;
42
+
43
+ export const ArtifactSnapshotManifestSchema = z
44
+ .object({
45
+ schemaVersion: z.literal(1),
46
+ name: z.string().regex(SNAPSHOT_NAME),
47
+ files: z.array(ArtifactSnapshotFileSchema),
48
+ fileCount: z.number().int().nonnegative(),
49
+ bytes: z.number().int().nonnegative(),
50
+ baseSnapshot: z.string().regex(SNAPSHOT_NAME).optional(),
51
+ reusedFiles: z.number().int().nonnegative(),
52
+ reusedBytes: z.number().int().nonnegative(),
53
+ })
54
+ .superRefine((manifest, ctx) => {
55
+ const paths = new Set(manifest.files.map((file) => file.path));
56
+ const bytes = manifest.files.reduce((total, file) => total + file.bytes, 0);
57
+ if (paths.size !== manifest.files.length) {
58
+ ctx.addIssue({ code: "custom", path: ["files"], message: "paths must be unique" });
59
+ }
60
+ if (manifest.fileCount !== manifest.files.length) {
61
+ ctx.addIssue({
62
+ code: "custom",
63
+ path: ["fileCount"],
64
+ message: "fileCount must equal files.length",
65
+ });
66
+ }
67
+ if (manifest.bytes !== bytes) {
68
+ ctx.addIssue({ code: "custom", path: ["bytes"], message: "bytes must equal file sizes" });
69
+ }
70
+ if (manifest.reusedFiles > manifest.fileCount || manifest.reusedBytes > manifest.bytes) {
71
+ ctx.addIssue({
72
+ code: "custom",
73
+ path: ["reusedFiles"],
74
+ message: "reused totals cannot exceed snapshot totals",
75
+ });
76
+ }
77
+ });
78
+ export type ArtifactSnapshotManifest = z.infer<typeof ArtifactSnapshotManifestSchema>;
79
+
80
+ export type ArtifactSnapshotContent = string | Uint8Array;
81
+ export type ArtifactSnapshotFiles = Readonly<Record<string, ArtifactSnapshotContent>>;
82
+
83
+ export interface WriteArtifactSnapshotOptions {
84
+ artifactsDir: string;
85
+ name: string;
86
+ files: ArtifactSnapshotFiles;
87
+ baseSnapshot?: string;
88
+ maxFiles?: number;
89
+ maxBytes?: number;
90
+ writeConcurrency?: number;
91
+ }
92
+
93
+ type PreparedSnapshotFile = ArtifactSnapshotFile & {
94
+ content: Buffer;
95
+ };
96
+
97
+ const positiveInteger = (value: number | undefined, fallback: number, label: string): number => {
98
+ const resolved = value ?? fallback;
99
+ if (!Number.isSafeInteger(resolved) || resolved <= 0) {
100
+ throw new RangeError(`${label} must be a positive safe integer`);
101
+ }
102
+ return resolved;
103
+ };
104
+
105
+ const snapshotPath = (source: string): string => {
106
+ if (!source || source.includes("\0") || source.includes("\\")) {
107
+ throw new Error(`snapshot file path is invalid: ${JSON.stringify(source)}`);
108
+ }
109
+ const withoutRoot = source.replace(/^\/+/, "");
110
+ if (Buffer.byteLength(withoutRoot) > MAX_SNAPSHOT_PATH_BYTES) {
111
+ throw new Error(`snapshot file path is too long: ${JSON.stringify(source)}`);
112
+ }
113
+ const normalized = posix.normalize(withoutRoot);
114
+ if (
115
+ !withoutRoot ||
116
+ normalized === "." ||
117
+ normalized === ".." ||
118
+ normalized.startsWith("../") ||
119
+ posix.isAbsolute(normalized) ||
120
+ normalized !== withoutRoot
121
+ ) {
122
+ throw new Error(`snapshot file path is unsafe: ${JSON.stringify(source)}`);
123
+ }
124
+ return normalized;
125
+ };
126
+
127
+ const contentBuffer = (content: ArtifactSnapshotContent): Buffer =>
128
+ typeof content === "string" ? Buffer.from(content, "utf8") : Buffer.from(content);
129
+
130
+ const prepareFiles = (
131
+ files: ArtifactSnapshotFiles,
132
+ maxFiles: number,
133
+ maxBytes: number,
134
+ ): PreparedSnapshotFile[] => {
135
+ const paths = new Set<string>();
136
+ const entries = Object.entries(files);
137
+ if (entries.length > maxFiles) {
138
+ throw new Error(`snapshot has ${entries.length} files, exceeding the ${maxFiles}-file limit`);
139
+ }
140
+ const prepared: PreparedSnapshotFile[] = [];
141
+ let bytes = 0;
142
+ for (const [sourcePath, content] of entries) {
143
+ const path = snapshotPath(sourcePath);
144
+ if (path === ARTIFACT_SNAPSHOT_MANIFEST) {
145
+ throw new Error(`snapshot file path is reserved: ${path}`);
146
+ }
147
+ if (paths.has(path)) {
148
+ throw new Error(`snapshot contains duplicate path: ${path}`);
149
+ }
150
+ paths.add(path);
151
+ const contentBytes =
152
+ typeof content === "string" ? Buffer.byteLength(content, "utf8") : content.byteLength;
153
+ bytes += contentBytes;
154
+ if (!Number.isSafeInteger(bytes) || bytes > maxBytes) {
155
+ throw new Error(`snapshot has ${bytes} bytes, exceeding the ${maxBytes}-byte limit`);
156
+ }
157
+ const buffer = contentBuffer(content);
158
+ prepared.push({
159
+ path,
160
+ content: buffer,
161
+ bytes: buffer.byteLength,
162
+ sha256: createHash("sha256").update(buffer).digest("hex"),
163
+ });
164
+ }
165
+ prepared.sort((left, right) => {
166
+ if (left.path < right.path) return -1;
167
+ if (left.path > right.path) return 1;
168
+ return 0;
169
+ });
170
+ return prepared;
171
+ };
172
+
173
+ const ensurePrivateParents = async (root: string, target: string): Promise<void> => {
174
+ const resolvedRoot = resolve(root);
175
+ const parent = dirname(target);
176
+ const path = relative(resolvedRoot, parent);
177
+ let cursor = resolvedRoot;
178
+ for (const part of path.split(sep).filter(Boolean)) {
179
+ cursor = resolve(cursor, part);
180
+ try {
181
+ await mkdir(cursor, { mode: 0o700 });
182
+ } catch (error) {
183
+ if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
184
+ }
185
+ const stats = await lstat(cursor);
186
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
187
+ throw new Error(`snapshot parent must be a real directory: ${cursor}`);
188
+ }
189
+ const canonical = await realpath(cursor);
190
+ if (canonical !== resolvedRoot && !canonical.startsWith(`${resolvedRoot}${sep}`)) {
191
+ throw new Error(`snapshot parent escaped its root: ${cursor}`);
192
+ }
193
+ await chmod(cursor, 0o700);
194
+ }
195
+ };
196
+
197
+ const writePrivateFile = async (root: string, path: string, content: Uint8Array): Promise<void> => {
198
+ const target = resolve(root, path);
199
+ if (!target.startsWith(`${resolve(root)}${sep}`)) {
200
+ throw new Error(`snapshot path escaped its root: ${path}`);
201
+ }
202
+ await ensurePrivateParents(root, target);
203
+ const handle = await open(
204
+ target,
205
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY | constants.O_NOFOLLOW,
206
+ 0o600,
207
+ );
208
+ try {
209
+ await handle.writeFile(content);
210
+ await handle.chmod(0o400);
211
+ } finally {
212
+ await handle.close();
213
+ }
214
+ };
215
+
216
+ type SnapshotBase = {
217
+ root: string;
218
+ files: Map<string, ArtifactSnapshotFile>;
219
+ };
220
+
221
+ export const readArtifactSnapshotManifest = async (
222
+ snapshotRoot: string,
223
+ ): Promise<ArtifactSnapshotManifest | undefined> => {
224
+ const rootStats = await lstat(snapshotRoot);
225
+ if (rootStats.isSymbolicLink() || !rootStats.isDirectory()) {
226
+ throw new Error(`artifact snapshot must be a real directory: ${snapshotRoot}`);
227
+ }
228
+ const manifestPath = join(snapshotRoot, ARTIFACT_SNAPSHOT_MANIFEST);
229
+ const manifestStats = await lstat(manifestPath).catch((error: unknown) => {
230
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return undefined;
231
+ throw error;
232
+ });
233
+ if (!manifestStats) return undefined;
234
+ if (
235
+ manifestStats.isSymbolicLink() ||
236
+ !manifestStats.isFile() ||
237
+ manifestStats.size > MAX_MANIFEST_BYTES
238
+ ) {
239
+ throw new Error(`artifact snapshot manifest is invalid: ${snapshotRoot}`);
240
+ }
241
+ return ArtifactSnapshotManifestSchema.parse(JSON.parse(await readFile(manifestPath, "utf8")));
242
+ };
243
+
244
+ const loadSnapshotBase = async (
245
+ root: string,
246
+ name: string | undefined,
247
+ ): Promise<SnapshotBase | undefined> => {
248
+ if (name === undefined) return undefined;
249
+ if (!SNAPSHOT_NAME.test(name)) {
250
+ throw new Error(`artifact base snapshot name is invalid: ${name}`);
251
+ }
252
+ const baseRoot = join(root, name);
253
+ const stats = await lstat(baseRoot);
254
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
255
+ throw new Error(`artifact base snapshot must be a real directory: ${name}`);
256
+ }
257
+ const manifest = await readArtifactSnapshotManifest(baseRoot);
258
+ if (!manifest) throw new Error(`artifact base snapshot manifest is missing: ${name}`);
259
+ if (manifest.name !== name) {
260
+ throw new Error(`artifact base snapshot manifest names ${manifest.name}, expected ${name}`);
261
+ }
262
+ return {
263
+ root: baseRoot,
264
+ files: new Map(manifest.files.map((file) => [file.path, file])),
265
+ };
266
+ };
267
+
268
+ const writeSnapshotFile = async (
269
+ root: string,
270
+ file: PreparedSnapshotFile,
271
+ base: SnapshotBase | undefined,
272
+ ): Promise<boolean> => {
273
+ const previous = base?.files.get(file.path);
274
+ if (base && previous?.bytes === file.bytes && previous.sha256 === file.sha256) {
275
+ const source = resolve(base.root, file.path);
276
+ const target = resolve(root, file.path);
277
+ if (
278
+ !source.startsWith(`${resolve(base.root)}${sep}`) ||
279
+ !target.startsWith(`${resolve(root)}${sep}`)
280
+ ) {
281
+ throw new Error(`snapshot path escaped its root: ${file.path}`);
282
+ }
283
+ let sourceMatches = false;
284
+ try {
285
+ const sourceHandle = await open(source, constants.O_RDONLY | constants.O_NOFOLLOW);
286
+ try {
287
+ const sourceStats = await sourceHandle.stat();
288
+ if (sourceStats.isFile() && sourceStats.size === file.bytes) {
289
+ const sourceContent = await sourceHandle.readFile();
290
+ sourceMatches = createHash("sha256").update(sourceContent).digest("hex") === file.sha256;
291
+ }
292
+ } finally {
293
+ await sourceHandle.close();
294
+ }
295
+ } catch (error) {
296
+ if (
297
+ error instanceof Error &&
298
+ "code" in error &&
299
+ (error.code === "ENOENT" || error.code === "ELOOP")
300
+ ) {
301
+ await writePrivateFile(root, file.path, file.content);
302
+ return false;
303
+ }
304
+ throw error;
305
+ }
306
+ if (sourceMatches) {
307
+ await ensurePrivateParents(root, target);
308
+ await copyFile(source, target, constants.COPYFILE_FICLONE);
309
+ await chmod(target, 0o400);
310
+ const copied = await readFile(target);
311
+ if (
312
+ copied.byteLength === file.bytes &&
313
+ createHash("sha256").update(copied).digest("hex") === file.sha256
314
+ ) {
315
+ return true;
316
+ }
317
+ try {
318
+ await unlink(target);
319
+ } catch (error) {
320
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) throw error;
321
+ }
322
+ }
323
+ }
324
+ await writePrivateFile(root, file.path, file.content);
325
+ return false;
326
+ };
327
+
328
+ const writeFiles = async (
329
+ root: string,
330
+ files: PreparedSnapshotFile[],
331
+ concurrency: number,
332
+ base: SnapshotBase | undefined,
333
+ ): Promise<Set<string>> => {
334
+ let cursor = 0;
335
+ const reused = new Set<string>();
336
+ const worker = async (): Promise<void> => {
337
+ while (cursor < files.length) {
338
+ const file = files[cursor];
339
+ cursor += 1;
340
+ if (file && (await writeSnapshotFile(root, file, base))) {
341
+ reused.add(file.path);
342
+ }
343
+ }
344
+ };
345
+ await Promise.all(
346
+ Array.from({ length: Math.min(concurrency, Math.max(1, files.length)) }, worker),
347
+ );
348
+ return reused;
349
+ };
350
+
351
+ const ensureArtifactRoot = async (artifactsDir: string): Promise<string> => {
352
+ await mkdir(artifactsDir, { recursive: true, mode: 0o700 });
353
+ const stats = await lstat(artifactsDir);
354
+ if (stats.isSymbolicLink() || !stats.isDirectory()) {
355
+ throw new Error(`artifact snapshot root must be a real directory: ${artifactsDir}`);
356
+ }
357
+ await chmod(artifactsDir, 0o700);
358
+ return await realpath(artifactsDir);
359
+ };
360
+
361
+ export const writeArtifactSnapshot = async ({
362
+ artifactsDir,
363
+ name,
364
+ files,
365
+ baseSnapshot,
366
+ maxFiles: requestedMaxFiles,
367
+ maxBytes: requestedMaxBytes,
368
+ writeConcurrency: requestedWriteConcurrency,
369
+ }: WriteArtifactSnapshotOptions): Promise<ArtifactSnapshotManifest> => {
370
+ if (!SNAPSHOT_NAME.test(name)) {
371
+ throw new Error(
372
+ "artifact snapshot name must start with a letter or number and contain only letters, numbers, dots, underscores, or hyphens",
373
+ );
374
+ }
375
+ const maxFiles = positiveInteger(requestedMaxFiles, DEFAULT_MAX_FILES, "snapshot maxFiles");
376
+ const maxBytes = positiveInteger(requestedMaxBytes, DEFAULT_MAX_BYTES, "snapshot maxBytes");
377
+ const writeConcurrency = positiveInteger(
378
+ requestedWriteConcurrency,
379
+ DEFAULT_WRITE_CONCURRENCY,
380
+ "snapshot writeConcurrency",
381
+ );
382
+ const prepared = prepareFiles(files, maxFiles, maxBytes);
383
+ const root = await ensureArtifactRoot(artifactsDir);
384
+ if (baseSnapshot === name) {
385
+ throw new Error("artifact snapshot cannot use itself as its base");
386
+ }
387
+ const base = await loadSnapshotBase(root, baseSnapshot);
388
+ const target = join(root, name);
389
+ try {
390
+ await lstat(target);
391
+ throw new Error(`artifact snapshot already exists: ${name}`);
392
+ } catch (error) {
393
+ if (!(error instanceof Error && "code" in error && error.code === "ENOENT")) {
394
+ throw error;
395
+ }
396
+ }
397
+
398
+ const staging = await mkdtemp(join(root, `.lux-snapshot-${name}-`));
399
+ await chmod(staging, 0o700);
400
+ try {
401
+ const reused = await writeFiles(staging, prepared, writeConcurrency, base);
402
+ const manifest: ArtifactSnapshotManifest = {
403
+ schemaVersion: 1,
404
+ name,
405
+ files: prepared.map(({ path, bytes, sha256 }) => ({
406
+ path,
407
+ bytes,
408
+ sha256,
409
+ })),
410
+ fileCount: prepared.length,
411
+ bytes: prepared.reduce((total, file) => total + file.bytes, 0),
412
+ ...(baseSnapshot ? { baseSnapshot } : {}),
413
+ reusedFiles: reused.size,
414
+ reusedBytes: prepared
415
+ .filter((file) => reused.has(file.path))
416
+ .reduce((total, file) => total + file.bytes, 0),
417
+ };
418
+ await writePrivateFile(
419
+ staging,
420
+ ARTIFACT_SNAPSHOT_MANIFEST,
421
+ Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`, "utf8"),
422
+ );
423
+ await rename(staging, target);
424
+ return manifest;
425
+ } catch (error) {
426
+ await rm(staging, { recursive: true, force: true });
427
+ throw error;
428
+ }
429
+ };